線程基礎 第三篇:多線程之間的通信


序:線程之間的通信,是多線程之間普遍存在的方法,接下來,我就根據自己所學,來一一講解一下:

線程之間的通信

一、為什么要線程通信

 

 1. 多個線程並發執行時, 在默認情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,

   並且我們希望他們有規律的執行, 那么多線程之間需要一些協調通信,以此來幫我們達到多線程共同操作一份數據

2.當然如果我們沒有使用線程通信來使用多線程共同操作同一份數據的話,雖然可以實現,

  但是在很大程度會造成多線程之間對同一共享變量的爭奪,那樣的話勢必為造成很多錯誤和損失!

3.所以,我們才引出了線程之間的通信,多線程之間的通信能夠避免對同一共享變量的爭奪。

 

二、什么是線程通信?

 

  多個線程在處理同一個資源,並且任務不同時,需要線程通信來幫助解決線程之間對同一個變量的使用或操作

     就是多個線程在操作同一份數據時, 避免對同一共享變量的爭奪

  於是我們引出了等待喚醒機制:(wait()notify()

  就是在一個線程進行了規定操作后,就進入等待狀態(wait), 等待其他線程執行完他們的指定代碼過后 再將其喚醒(notify);

(1)wait()方法:

   在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致當前線程等待

    線程調用wait()方法,釋放它對鎖的擁有權,然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。

    要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。

(2)notif()方法:

 notify()方法會喚醒一個等待當前對象的鎖的線程。喚醒在此對象監視器上等待的單個線程。

(3)notifAll()方法:
  notifyAll()方法會喚醒在此對象監視器上等待的所有線程。
  
(4)如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是由於調用了wait方法中的一個)

 notify()方法應該是被擁有對象的鎖的線程所調用。

(5)以上方法都定義在類:Object中

1.因為,這些方法在操作同步中的線程的時候,都必須標示其所操作線程所持有的鎖(被該鎖的對象調用),

而只有同一個對象監視器下(同一個鎖上)的被等待線程,可以被持有該鎖的線程喚醒,(無法喚醒不同鎖上的線程)

2.所以,等待和喚醒的必須是同一個對象的監視器下(同一個鎖上)的線程。
而鎖可以是任意已近確定的對象, 能被任意對象調用的方法應當定義在 Object類中。

注:
監視器(鎖):同一個對象的監視器下(同一個鎖上)的線程,一次只能執行一個:就是擁有監視器所有權(持有鎖)的那一個線程。 

三、實現線程通信的小demo

      

 如大眾所說,體現線程通信的鮮明的例子:生產者---消費者是最明顯的例子之一。那我們接下來敲一個關於生產者和消費者的簡單的小demo,來演示一下線程的通信:

 

 (1)面包的類(生產和消費方法都在里面哦)

/**
 *@functon 線程通信之面包類 
 *@author 溫煦(昵稱:沉淪之巔)
 *@time 2017.12.5 
 */

package ThreadMessage;

public class Breads {
    
    //面包的id
    private int bid;
    //面包的個數
    private int num;
    
    //生產面包的方法(由於是demo,方便大家理解,就把synchronized關鍵字加到方法上面了哦)
    public synchronized void produc(){
        
        //當面包的數量不為0時,該方法處於等待狀態
        if(0 != num){
            try {
                wait();//等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //當面包數量為0時,那么就開始生產面包了哦
        num  =  num +1;//數量加1
        bid = bid + 1 ;//id當然也得加1
        String threadname = Thread.currentThread().getName();
        System.out.println(threadname+"生產了一個編號為"+bid+"的面包!");
        notify();//當執行完后,去喚醒其他處於等待的線程
    }
    //消費面包的方法
    public synchronized void consume(){
        //當面包的數量為0時,該方法處於等待狀態
        if(num == 0 ){
            try {
                wait();//等待
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        //消費完面包了,所以面包數量降為0了
        num  =  num -1;//數量減1
        String name1 = Thread.currentThread().getName();
        System.out.println(name1+"買了一個面包編號為"+bid);
        notify();//當執行完后,去喚醒其他處於等待的線程
    }

    
    //set和get方法
    public int getBid() {
        return bid;
    }

    public void setBid(int bid) {
        this.bid = bid;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    //有參構造
    public Breads(int bid, int num) {
        super();
        this.bid = bid;
        this.num = num;
    }

    //無參構造
    public Breads() {
        super();
        // TODO Auto-generated constructor stub
    }
}

 

(2)生產面包的類

/**
 *@functon 線程通信之生產類(繼承Thread類) 
 *@author 溫煦(昵稱:沉淪之巔)
 *@time 2017.12.5 
 */

package ThreadMessage;

public class producer extends Thread{
    
    //獲得面包的類
    private Breads bre ;

    //無參構造
    public producer() {
        super();
    }

    //有參構造
    public producer(Breads bre) {
        super();
        this.bre = bre;
    }


    //set和get方法
    public Breads getBre() {
        return bre;
    }

    public void setBre(Breads bre) {
        this.bre = bre;
    }

    //繼承重寫run方法
    @Override
    public void run() {
        pro();
    }
    
    //生產面包
    private void pro() {
        // 本系統默認循環生產20個面包
        for (int i = 0; i <20; i++) {
            try {
                //沉睡0.3秒(演示效果需要,可以不加)
                Thread.currentThread().sleep(300);
            } catch (InterruptedException e) {         
                e.printStackTrace();
            }
            //調用面包類里的生產面包的方法
            bre.produc();
        }
    }
}

   (3)消費面包的類

/**
 *@functon 線程通信之消費類(繼承Thread類) 
 *@author 溫煦(昵稱:沉淪之巔)
 *@time 2017.12.5 
 */

package ThreadMessage;

public class consume extends Thread{

    //獲得面包的類
    private Breads bre ;
    
    //set和get方法
    public Breads getBre() {
        return bre;
    }
    public void setBre(Breads bre) {
        this.bre = bre;
    }
    
    //繼承重寫run方法
    @Override
    public void run() {
        con();
    }
    
    //消費面包
    private void con() {
        // 與生產者保持一致,本系統默認循環生產20個面包(生產幾個,消費幾個)
        for(int i = 0;i<20;i++){
            try {
                //沉睡0.3秒(演示效果需要,可以不加)
                Thread.currentThread().sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //調用面包類里的生產面包的方法
            bre.consume();
        }
    }

    //有參構造
    public consume(Breads bre) {
        super();
        this.bre = bre;
    }

    //無參構造
    public consume() {
        super();
    }
}

 

 (4)測試類

/**
 *@functon 線程通信之測試類 
 *@author 溫煦(昵稱:沉淪之巔)
 *@time 2017.12.5 
 */

package ThreadMessage;

public class TestBreads {
    
    public static void main(String[] args) {
        
        //new一個面包類
        Breads bre = new Breads();
        
        //new一個生產者類
        producer proth = new producer(bre);
        //new一個消費者類
        consume conth = new consume(bre);
        
        //new一個包含消費者類的線程
        Thread t1 = new Thread(proth,"生產者");
        
        //new一個包含生產者類的線程
        Thread t2 = new Thread(conth,"消費者");
        
        //啟動線程
        t1.start();
        t2.start();
        
    }
}

 

   (5)演示效果(因數據太多,中間就省略了哦)

生產者生產了一個編號為1的面包!
消費者買了一個面包編號為1
生產者生產了一個編號為2的面包!
消費者買了一個面包編號為2
生產者生產了一個編號為3的面包!
消費者買了一個面包編號為3

...
...

 生產者生產了一個編號為17的面包!
 消費者買了一個面包編號為17
 生產者生產了一個編號為18的面包!
 消費者買了一個面包編號為18
 生產者生產了一個編號為19的面包!
 消費者買了一個面包編號為19
 生產者生產了一個編號為20的面包!
 消費者買了一個面包編號為20

 

   如上所示,案例表明生產者消費者之間就是運用了wait()和notify()這兩個方法,通過通信的沉睡與喚醒機制來完成兩個不同線程操作統一數據之間的通信,

當生產者生產出一個面包時,就會陷入沉睡,隨后立即去喚醒消費者,就像是對他說,你去買吧,我生產好了,然后消費者就會屁顛屁顛的去買了那個面包,當他吃完那個面包后,

也會陷入沉睡,隨后立即喚醒生產者,就像是對他說,你再生產一個吧,我吃完了,然后生產者就... ... 如此循環,周而復始,直到for循環結束為止。

  

四、多個生產者和消費者

  當我們創建多個生產者和消費者時,上述的代碼就會出現一個問題,就是他們無法直到到底要喚醒哪一個,所以這時候我們就用到了notifAll()方法。

  那么代碼就不一一再發了,與上面的差不多,唯一不一樣的就是在面包類里的if換成while循環判斷,notif()換成notifAll()。

  然后再給大家寫一下多個生產者和消費者的測試類:

  (1)多個生產者和消費者的測試類

/**
 *@functon 線程通信之測試類 
 *@author 溫煦(昵稱:沉淪之巔)
 *@time 2017.12.5 
 */

package ThreadMessage;

public class TestBreads {
    
    public static void main(String[] args) {
        
        //new一個面包類
        Breads bre = new Breads();
        
        //new一個生產者類
        producer proth = new producer(bre);
        //new一個消費者類
        consume conth = new consume(bre);
        
        //new三個包含消費者類的線程
        Thread t1 = new Thread(proth,"生產者1");
        Thread t3 = new Thread(proth,"生產者2");
        Thread t5 = new Thread(proth,"生產者3");
        
        //new三個包含生產者類的線程
        Thread t2 = new Thread(conth,"消費者1");
        Thread t4 = new Thread(conth,"消費者2");
        Thread t6 = new Thread(conth,"消費者3");
        
        //啟動線程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
    }
}

 

 (2)演示多個生產者和消費者通信效果

 

生產者1生產了一個編號為1的面包!
消費者3買了一個面包編號為1
生產者2生產了一個編號為2的面包!
消費者2買了一個面包編號為2
生產者3生產了一個編號為3的面包!
消費者1買了一個面包編號為3
生產者1生產了一個編號為4的面包!
消費者3買了一個面包編號為4

...
...

生產者3生產了一個編號為56的面包!
消費者3買了一個面包編號為56
生產者2生產了一個編號為57的面包!
消費者1買了一個面包編號為57
生產者1生產了一個編號為58的面包!
消費者2買了一個面包編號為58
生產者3生產了一個編號為59的面包!
消費者3買了一個面包編號為59
生產者2生產了一個編號為60的面包!
消費者1買了一個面包編號為60

    如上所示,我們創建了三個生產者和三個消費者,所以我們for循環為三次,也就是會生產和消費各60個面包;

  當進程開始后,會隨機開啟一個生產者線程生產一個面包后,陷入沉睡,隨后會隨機喚醒一個消費者線程,

  接着消費掉剛剛生產的那個面包,再次陷入沉睡,隨機喚醒一個生產者線程...  周而復始,直到結束。

 

  結束語:今天的線程通信通信就到這里了,如果你還有什么疑問可以隨時聯系我,當然非常希望閱讀了本篇文章的讀者,都能得到幫助,

      最后祝願各位在以后線程的學習道路上芝麻開花節節高。


免責聲明!

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



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