線程之間的通訊

來源:互聯網
上載者:User

標籤:分享圖片   生產者和消費者   div   統一   pack   ret   pre   class   運用   

一、為什麼要線程通訊?

 

 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個麵包;

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

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

 

線程之間的通訊

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.