JAVA多線程的使用方法(從代碼理解定義)


1、多線程的創建

注意:線程開啟不一定立即執行,由CPU調度執行

1.1創建方式一

繼承Thread類,重寫run()方法,調用start()方法開啟線程。

package SunThread;

public class Thread_01 extends Thread{
    @Override
    public void run() {
        //run方法線程體
        for (int i = 0; i < 20; i++) {
            System.out.println("我在寫代碼!");
        }
    }

    public static void main(String[] args) {
        //main線程,主線程
        //創建線程對象
        Thread_01 thread_01 = new Thread_01();
        //調用start開啟線程
        thread_01.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("我在學JAVA");
        }
    }
}

image-20201031134111826

多次運行發現(最好修改循環輸出次數多一點),輸出結果一般會不同,原因就是CPU在進行線程的調度。

1.2創建方式二(建議使用)

實現Runnable接口,重寫run()方法,實現接口需要丟入Runnable接口實現類,調用start()方法開啟線程。

package SunThread;

public class Thread_03 implements Runnable{
    @Override
    public void run() {
        //run方法線程體
        for (int i = 0; i < 20; i++) {
             System.out.println("我在寫代碼");
        }
    }

    public static void main(String[] args) {
        //main線程,主線程
        //創建ruunnable實現類對象
        Thread_03 thread_03 = new Thread_03();
        //創建線程對象通過線程對象實現我們的接口類
		//Thread thread = new Thread(thread_03);
		//thread.start();
        new Thread(thread_03).start();//與上兩行實現的結果是一樣的
        for (int i = 0; i < 100; i++) {
           System.out.println("我在學JAVA");
        }
    }
}

image-20201031134702704

推薦使用:實現Runable()接口,因為可以有效的避免單繼承的局限性,靈活方便,方便同一個對象被多個線程使用

1.3創建方式三(不經常使用)

​ 與使用runnable方式相比,callable功能更強大些:call方法可以有返回值,方法可以拋出異常,支持泛型的返回值,需要借助FutureTask類,獲取返回結果等。

運用三個線程下載網絡圖片的方式,對Callable實現接口類的方式進行解釋,(代碼直接復制運行不了)。

package SunThread;

import org.apache.commons.io.FileUtils;


import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class Thread_05 implements Callable<Boolean> {
    private String url;//下載的網絡圖片地址
    private String name;//保存的文件名

    public Thread_05(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.Downloader(url,name);
        System.out.println("文件name為"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new 3個Callable接口的對象
        Thread_05 t1 = new Thread_05("https://img-blog.csdnimg.cn/20201029222157503.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NVTl9fQ0dK,size_16,color_FFFFFF,t_70#pic_center","1.jpg");
        Thread_05 t2 = new Thread_05("https://img-blog.csdnimg.cn/20201029222523262.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NVTl9fQ0dK,size_16,color_FFFFFF,t_70#pic_center","2.jpg");
        Thread_05 t3 = new Thread_05("https://img-blog.csdnimg.cn/20201029222403840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NVTl9fQ0dK,size_16,color_FFFFFF,t_70#pic_center","3.jpg");
        //創建固定線程個數為3個的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //提交執行線程,最多3個
        Future<Boolean> s1 = executorService.submit(t1);
        Future<Boolean> s2 = executorService.submit(t2);
        Future<Boolean> s3 = executorService.submit(t3);
        //獲取相應的結果
        boolean rs1=s1.get();
        boolean rs2=s2.get();
        boolean rs3=s3.get();
        //關閉線程池
        executorService.shutdown();
    }
}
//具體的下載方法,使用時需要導入commons—IO-2.6.jar包
class WebDownloader{
    public void Downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image-20201031151212646

注:中間並發進行的接口對象,根據自己需要進行修改。

2、線程五大狀態

2.1線程的停止(stop)

1.建議線程正常停止―>利用次數,不建議死新環.

2.建議使用標志位--->設置一個標志位

3.不要使用stop或者destroy等過時或者JDK不建議使用的方法

package SunThread;
//測試線程停止
public class Threadstop_01 implements Runnable{
    private boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("我在測試線程停止"+i++);
        }
    }
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        Threadstop_01 threadstop_01 = new Threadstop_01();

        new Thread(threadstop_01).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if(i==900)
            {
                threadstop_01.stop();
                System.out.println("線程停止了");
            }
        }

    }
}

image-20201031165812618

2.2線程的休眠(sleep)

1、sleep(時間)指定當前線程阻塞的毫秒數;

2、sleep存在異常InterruptedException;

3、sleep時間達到后線程進入就緒狀態;

4、sleep可以模擬網絡延時,倒計時等。

5、每一個對象都有一個鎖,sleep不會釋放鎖;

package SunThread;
//runnable實現買票過程
//sleep放大問題的發生性
public class Thread_04 implements Runnable{
    private int a=10;
    @Override
    public void run() {
        while(a>0){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+a--+"張票");
        }
    }

    public static void main(String[] args) {
        Thread_04 thread_04 = new Thread_04();
        new Thread(thread_04,"小明").start();
        new Thread(thread_04,"小李").start();
        new Thread(thread_04,"小紅").start();

    }
}

image-20201031171313334

注:sleep放大問題的發生性,運用sleep可以編寫倒計時、模擬網絡延時等等。

2.3線程的禮讓(yield)

1、禮讓線程,讓當前正在執行的線程暫停,但不阻塞;

2、將線程從運行狀態轉為就緒狀態;

3、讓CPU重新調度,禮讓不一定成功!看CPU心情

package SunThread;

import javax.xml.transform.sax.SAXSource;

public class ThreadYield_01 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"該線程開始執行!");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"該線程停止執行!");
    }
    public static void main(String[] args) {
        ThreadYield_01 threadYield_01 = new ThreadYield_01();
        new Thread(threadYield_01,"A").start();
        new Thread(threadYield_01,"B").start();
    }
}

image-20201031172234766

禮讓不一定會成功

2.4線程的強制執行(join)

1、Join合並線程,待此線程執行完成后,再執行其他線程,其他線程阻塞

2、可以想象成插隊

package SunThread;

public class ThreadJoin_01 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println("線程vip來了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadJoin_01 threadJoin_01 = new ThreadJoin_01();
        Thread thread = new Thread(threadJoin_01);

        thread.start();

        for (int i = 0; i < 1000; i++) {
            if(i==500)
            {
                thread.join();
            }
            System.out.println("main"+i);
        }
    }
}

image-20201031174008986

由於現在CPU一般多核進行處理,我們可以在run()方法中設置一個休眠時間。

3、線程的狀態

3.1運用代碼的輸出線程狀態

1、NEW 尚未啟動的線程處於此狀態。

2、RUNAABLE 在Java虛擬機中執行的線程處於此狀態。

3、BLOCKED 被阻塞等待監視器鎖定的線程處於此狀態。
4、WAITING 正在等待另一個線程執行特定動作的線程處於此狀態。

5、TIMED_WAITING 正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態。

6、TEPM4INATED 已退出的線程處於此狀態。

package SunThread;

public class Thread_06 {
    public static void main(String[] args) throws InterruptedException {
        //運用lambda表達式對方法進行簡化
        Thread thread=new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("==========");
        });
        //觀察狀態
        Thread.State state = thread.getState();
        System.out.println(state);//期待輸出NEW
        //觀察啟動后
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUN
        while(state!=thread.getState().TERMINATED)
        {
            thread.sleep(200);
            state = thread.getState();
            System.out.println(state);//RUN
        }
    }
}

image-20201031215226449

4、線程的優先級

4.1設置優先級並輸出

Java提供一個線程調度器來監控程序中啟動后進入就緒狀態的所有線程,線程調度器按照優先級決定應該調度哪個線程來執行。

線程的優先級用數字表示,范圍從1~10.

Thread.MIN_PRIORITY= 1;

Thread.MAX_PRIORITY= 10;

Thread.MAX_PRIORITY= 10;

getPriority() . setPriority(int xxx)

優先級的設定建議在start()調度前

package SunThread;

public class ThreadPriority_01 {
    public static void main(String[] args) {
        //主線程優先級
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY=10
        t4.start();
        t5.setPriority(8);
        t5.start();
        t6.setPriority(7);
        t6.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

image-20201031222746469

優先級低只是意味着獲得調度的概率低.並不是優先級低就不會被調用了.這都是看CPU的調度

優先級的設定建議在start()調度

5、線程的守護

5.1代碼解釋線程的守護

1、線程分為用戶線程和守護線程

2、虛擬機必須確保用戶線程執行完畢

3、虛擬機不用等待守護線程執行完畢

4、如,后台記錄操作日志,監控內存,垃圾回收等待!.

package SunThread;

public class ThreadDaemon_01 {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread thread = new Thread(god);
        thread.setDaemon(true);//默認為false,默認為用戶線程,設置為守護線程
        thread.start();
        new Thread(you).start();

    }
}
class God implements Runnable{
    @Override
    public void run() {
        while (true)
        {
            System.out.println("上帝守護者你!");
        }
    }
}
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("生活了"+i+"天");
        }
        System.out.println("goodbye world!");
    }
}

image-20201031230630145

6、線程的同步機制

6.1線程的同步理解

​ 由於我們可以通過private 關鍵字來保證數據對象只能被方法訪問﹐所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊.

​ 同步方法:public synchronized void method(int args)0{}

​ synchronized方法控制對“對象”的訪問,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行﹐否則線程會阻塞,方法一旦執行﹐就獨占該鎖,直到該方法返回才釋放鎖﹐后面被阻塞的線程才能獲得這個鎖,繼續執行.

缺陷:若將一個大的方法申明為synchronized將會影響效率

6.2線程的不安全性

用到lambda表達式簡化https://blog.csdn.net/SUN__CGJ/article/details/109406652

package SunThread;

import java.util.ArrayList;

public class Thread_07 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            //lambda表達式簡化
            new Thread(()->{list.add(Thread.currentThread().getName())}).start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

image-20201101100340495

期待輸出9999,但是輸出只有9994,這就是線程的不安全性。

6.3實現線程同步

同步塊 :synchronized (Obj ){}

Obj稱之為同步監視器
Obj可以是任何對象﹐但是推薦使用共享資源作為同步監視器
同步方法中無需指定同步監視器﹐因為同步方法的同步監視器就是this ,就是這個對象本身﹐或者是class [反射中講解]
同步監視器的執行過程
1.第一個線程訪問﹐鎖定同步監視器﹐執行其中代碼.

2第二個線程訪問﹐發現同步監視器被鎖定﹐無法訪問.

3.第一個線程訪問完畢﹐解鎖同步監視器.
4.第二個線程訪問,發現同步監視器沒有鎖,然后鎖定並訪問

package SunThread;

import java.util.ArrayList;

public class Thread_07 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            //lambda表達式簡化
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

image-20201101101343864

這樣就是與我們的期待值相符合了。簡單理解就是加鎖,鎖的是所需要操作的對象。

補充:

運用JUC並發編程實現同步。

package SunThread;

import java.util.concurrent.CopyOnWriteArrayList;

public class Thread_08 {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

image-20201101103950577

7、線程的死鎖狀態

7.1死鎖

​ 多個線程各自占有一些共享資源﹐並且互相等待其他線程占有的資源才能運行﹐而導致兩個或者多個線程都在等待對方釋放資源﹐都停止執行的情形.某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題.

產生死鎖的四個必要條件:
1.互斥條件:一個資源每次只能被一個進程使用。

2.請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

3.不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。

4.循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。

用兩個小孩想吃蘋果和香蕉進行解釋

package SunThread;

public class DeadLock_01 {
    public static void main(String[] args) {
        Eat e1 = new Eat(0, "小明");
        Eat e2 = new Eat(2, "小紅");
        e1.start();
        e2.start();

    }
}
class Apple{ }
class Banana{ }
class Eat extends Thread{
    static Apple apple=new Apple();
    static Banana banana=new Banana();
    int choice;
    String name;
     Eat(int choice,String name){
        this.choice=choice;
        this.name=name;
     }
    @Override
    public void run() {
        try {
            eating();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void eating() throws InterruptedException {
         if(choice==0){
             synchronized (apple){
                 System.out.println(this.name+"獲得蘋果的鎖");
                 Thread.sleep(1000);
                 synchronized (banana){
                     System.out.println(this.name+"獲得香蕉的鎖");
                 }
             }

         }
         else{
             synchronized (banana){
             System.out.println(this.name+"獲得香蕉的鎖");
                 Thread.sleep(2000);
                 synchronized (apple){
                     System.out.println(this.name+"獲得蘋果的鎖");
                 }
             }
         }
    }
}

image-20201101113257580

期待輸出:

小明獲得蘋果的鎖
小紅獲得香蕉的鎖
小明獲得香蕉的鎖
小紅獲得蘋果的鎖

但是輸出的不一樣,而且程序在一直運行。

解決方法:

package SunThread;

public class DeadLock_01 {
    public static void main(String[] args) {
        Eat e1 = new Eat(0, "小明");
        Eat e2 = new Eat(2, "小紅");
        e1.start();
        e2.start();

    }
}
class Apple{ }
class Banana{ }
class Eat extends Thread{
    static Apple apple=new Apple();
    static Banana banana=new Banana();
    int choice;
    String name;
     Eat(int choice,String name){
        this.choice=choice;
        this.name=name;
     }
    @Override
    public void run() {
        try {
            eating();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void eating() throws InterruptedException {
         if(choice==0){
             synchronized (apple){
                 System.out.println(this.name+"獲得蘋果的鎖");
                
             }
             Thread.sleep(1000);
             synchronized (banana){
                 System.out.println(this.name+"獲得香蕉的鎖");
             }

         }
         else{
             synchronized (banana){
             System.out.println(this.name+"獲得香蕉的鎖");
                 
             }
             Thread.sleep(2000);
             synchronized (apple){
                 System.out.println(this.name+"獲得蘋果的鎖");
             }
         }
    }
}

image-20201101113435853

7、Lock鎖

​ 從JDK 5.0開始,Java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步。同步鎖使用Lock對象充當

​ java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象

​ ReentrantLock類實現了Lock,它擁有與synchronized相同的並發性和內存語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。

​ 買票的例子進行解釋。

package SunThread;

import java.util.concurrent.locks.ReentrantLock;

public class TeskLock_01 {
    public static void main(String[] args) {
        TeskLock teskLock = new TeskLock();
        new Thread(teskLock, "小明").start();
        new Thread(teskLock, "小李").start();
        new Thread(teskLock, "小紅").start();
    }
}
    class TeskLock implements Runnable {
        private int a = 10;
        //定義lock
        private final ReentrantLock lock = new ReentrantLock();
        @Override
        public void run() {
            while (true) {
                lock.lock();//加鎖
                try {
                    if(a>0) {
                        Thread.sleep(200);
                        System.out.println(Thread.currentThread().getName() + "拿到了第" + a-- + "張票");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//解鎖
                }
            }
        }
    }


image-20201101115845129

符合常理。

8、生產者與消費者問題

8.1應用場景:生產者消費者問題

  • 1、假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費.

  • 2、如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止.

  • 3、如果產品中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫再次放入產品為止.

  • Java提供了幾個方法解決線程之間的通信問題

  • image-20201101191636901

8.2管程法進行實現

 生產者:負責生產數據的模塊(可能是方法,對象,線程,進程)

 消費者:負責處理數據的模塊(可能是方法,對象,線程,進程)

 緩沖區:消費者不能直接使用生產者的數據,他們之間有個緩沖區,生產者將生產好的數據放入緩沖區,消費者從緩沖區拿出數據

樣例思路:

1.首先有一個生產者,消費者、生產者只顧生產,消費者只管消費、

2.利用了一個緩沖區,緩沖了一個10個大小的數組

3.有個方法叫放入產品,產品丟進來的時候,我們判斷一下緩沖區有沒有滿,如果滿了的話,生產者就要等待了,

如果沒有滿,就將產品放進去,放進去之后有產品了,趕緊通知消費者消費

4.消費者就判斷下能不能消費呢,有沒有東西,有東西的話,我就可以直接消費,消費完了,就趕緊通知生產者生產。

如果沒有東西呢,消費者就等待。等待生產者去通知他,生產者通知了,他就可以解除等待了

package SunThread;
//測試: 生產者消費者模型-->利用緩沖區解決:管程法
//生產者 , 消費者 , 產品 , 緩沖區
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生產者
class Productor extends Thread {
    SynContainer container;
    public Productor(SynContainer container) {
        this.container = container;
    }
    //生產
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生產了" + i + "只雞");
        }
    }
}

//消費者
class Consumer extends Thread {
    SynContainer container;
    public Consumer(SynContainer container) {
        this.container = container;
    }
    //消費
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費了-->" + container.pop().id + "只雞");
        }
    }
}
//產品
class Chicken {
    int id;//編號
    public Chicken(int id) {
        this.id = id;
    }
}

//緩沖區
class SynContainer {
    //需要一個容器大小
    Chicken[] chickens = new Chicken[10];
    //容器計數器
    int count = 0;
    //生產者放入產品
    public synchronized void push(Chicken chicken) {
        //如果容器滿了,就需要等待消費者消費
        if (count == chickens.length) {
            //生產者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果沒有滿,我們需要丟入產品
        chickens[count] = chicken;
        count++;
        //可以通知消費者消費了.
        this.notifyAll();
    }
    //消費者消費產品
    public synchronized Chicken pop() {
        //判斷能否消費
        if (count == 0) {
            //消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消費
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生產者生產
        this.notifyAll();
        return chicken;
    }
}

image-20201101191044408

8.3信號燈法

​ 來判斷一個標志位flag,如果為true,就讓他等待、如果為false,就讓他去通知另外一個人、把兩人銜接起來,就像咱們的信號燈紅燈停,綠燈行,通過這樣一個判斷方式,只要來判斷什么時候讓他等待,什么時候將他喚醒就ok。

package SunThread;

//測試生產者消費者問題2:信號燈法,通過標志位解決

public class TestPC_01 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生產者-->演員
class Player extends Thread {
    TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("快樂大本營播放中");
            } else {
                this.tv.play("抖音:記錄美好生活");
            }
        }
    }
}

//消費者-->觀眾
class Watcher extends Thread {
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

//產品-->節目
class TV {
    //演員表演,觀眾等待 T
    //觀眾觀看,演員等待 F
    String voice; // 表演的節目
    boolean flag = true;
    //表演
    public synchronized void play(String voice) {

        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演員表演了:" + voice);
        //通知觀眾觀看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }
    //觀看
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀看了:" + voice);
        //通知演員表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

image-20201101192234909

9、線程池

9.1用Runable實現線程池

  1. 背景:經常創建和銷毀、使用量特別大的資源,比如並發情況下的線程,對性能影響很大

  2. 思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀、實現重復利用。類似生活中公共交通工具。

  3. 好處:

    提高響應速度(減少了創建線程的時間)

    降低資源的消耗(重復利用線程池中線程,不需要每次都創建)

    便於線程管理(...)

    corePoolSize:核心池的大小

    maximumPoolSize:最大線程數

    keepAliveTime:線程沒有任務時最多保持多長時間后會終止

  4. JDK 5.0起提供了線程池相關API:ExecutorService 和 Executors

  5. ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor

  • void execute(Runnable command):執行任務/命令,沒有返回值,一 般用來執行Runnable

  • Future submit(Callable task):執行任務,有返回值,一般用來執行Callable

  • void shutdown():關閉連接池

    6.Executors:工具類、線程池的工廠類、用於創建並返回不同類型的線程池

用Runable實現線程池

package SunThread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//測試線程池
public class TestPool {

    public static void main(String[] args) {
        //1.創建服務,創建線程池
        //newFixedThreadPool(10); 參數為線程池的大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //執行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.關閉連接
        service.shutdown();
    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

}

自學習於嗶哩嗶哩狂神說JAVA(侵刪);

作為自己的筆記,歡迎大家斧正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM