線程之間的通信


一、為什么要線程通信?

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

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

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

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

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

二、什么是線程通信?

 

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

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

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

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

(1)wait()方法:

    線程調用wait()方法,釋放它對鎖的擁有權,同時他會在等待的位置加一個標志,為了以后使用notify()或者notifyAll()方法  喚醒它時,它好能從當前位置獲得鎖的擁有權,變成就緒狀態,

    要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。  在哪里等待被喚醒時,就在那里開始執行。

(1)notify/notifyAll()方法:

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

 notifAll()方法: notifyAll()方法會喚醒在此對象監視器上等待的所有線程。

 當執行notify/notifyAll方法時,會喚醒一個處於等待該 對象鎖 的線程,然后繼續往下執行,直到執行完退出對象鎖鎖住的區域(synchronized修飾的代碼塊)后再釋放鎖。

從這里可以看出,notify/notifyAll()執行后,並不立即釋放鎖,而是要等到執行完臨界區中代碼后,再釋放。故,在實際編程中,我們應該盡量在線程調用notify/notifyAll()后,立即退出臨界區。即不要在notify/notifyAll()后面再寫一些耗時的代碼。

 

 三、實現線程通信的小demo

  

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

 

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

  

/**
*@ClassName Breads
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:19
*@Version 1.0
**/
@Data
public class Breads {
//面包的id
private int bid;
//面包的個數
private int num;

//生產面包的方法(由於是demo,方便大家理解,就把synchronized關鍵字加到方法上面了哦)
public synchronized void produc(){

//當面包的數量不為0時,該方法處於等待狀態
     //多對多的時候 while
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+"的面包!個數為"+num);
notify();//當執行完后,去喚醒其他處於等待的線程
     //多對多的時候 notifyAll()

}
//消費面包的方法
public synchronized void consume(){
//當面包的數量為0時,該方法處於等待狀態
    //多對多的時候 while
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+"個數為"+num);
notify();//當執行完后,去喚醒其他處於等待的線程
    //多對多的時候notifyAll()
}
//有參構造
public Breads(int bid, int num) {
super();
this.bid = bid;
this.num = num;
}

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

 (2)生產面包的類

/**
*@ClassName producer
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:22
*@Version 1.0
**/
@Data
public class producer implements Runnable {
//獲得面包的類
private Breads bre ;
//無參構造
public producer() {
super();
}
//有參構造
public producer(Breads bre) {
super();
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)消費面包的類
/**
*@ClassName consume
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:24
*@Version 1.0
**/
@Data
public class consume implements Runnable {
//獲得面包的類
private Breads 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)測試類
/**
*@ClassName TestBreads
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:25
*@Version 1.0
**/
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)演示效果

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

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

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

四、多個生產者和消費者

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

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

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

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

    

/**
*@ClassName TestBreadss
*@Description TODD
*@AUTHOR sh-wangbs
*@Date 2019/2/279:26
*@Version 1.0
**/
public class TestBreadss {

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)演示多個生產者和消費者通信效果

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

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

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

 本文參考:

  https://www.cnblogs.com/hapjin/p/5492645.html      

  https://www.cnblogs.com/Wenxu/p/7979023.html

如果有什么沖突,請告知我,我可以對文章進行修改

 


免責聲明!

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



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