欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解

 更新時間:2017年10月26日 14:30:34   作者:name_s_jimmy  
這篇文章主要介紹了Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解,具有一定參考價值,需要的朋友可以了解下。

前言

前面的例子都是多個線程在做相同的操作,比如4個線程都對共享數(shù)據(jù)做tickets–操作。大多情況下,程序中需要不同的線程做不同的事,比如一個線程對共享變量做tickets++操作,另一個線程對共享變量做tickets–操作,這就是大名鼎鼎的生產(chǎn)者和消費者模式。

正文

一,生產(chǎn)者-消費者模式也是多線程

生產(chǎn)者和消費者模式也是多線程的范例。所以其編程需要遵循多線程的規(guī)矩。

首先,既然是多線程,就必然要使用同步。上回說到,synchronized關鍵字在修飾函數(shù)的時候,使用的是“this”鎖,所以在同一個類中的函數(shù)被synchronized修飾后,使用的是同一把鎖。線程調(diào)用這些函數(shù)時,不管調(diào)用的是tickets++操作函數(shù),還是tickets–函數(shù),都會先去判斷是否加鎖了,得到鎖之后再去進行具體的操作。

我們先用代碼把程序中的資源,生產(chǎn)者,消費者表示出來。

package com.jimmy.ThreadCommunication;
class Resource{  // 資源類
  private String productName; // 資源名稱
  private int count = 1;    // 資源編號
  public void produce(String name){  // 生產(chǎn)資源函數(shù)
    this.productName = name + count;
    count ++;  // 資源編號遞增,用來模擬資源遞增
    System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者.."+this.productName);
  }
  public void consume() { // 消費資源函數(shù)
    System.out.println(Thread.currentThread().getName()+"...消費者.."+this.productName);    
  }
}
class Producer implements Runnable{ // 生產(chǎn)者類,用于開啟生產(chǎn)者線程
  private Resource res;
  //生產(chǎn)者初始化就要分配資源
  public Producer(Resource res) {  
    this.res = res;
  }
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {     
      res.produce("bread");   // 循環(huán)生產(chǎn)10次
    }
  }
}
class Comsumer implements Runnable{  // 消費者類,用于開啟消費者線程
  private Resource res;
  //同理,消費者一初始化也要分配資源
  public Comsumer(Resource res) {
    this.res = res;
  }
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {     
      res.consume(); // 循環(huán)消費10次
    }
  }
}
public class ProducerAndConsumer1 {
  public static void main(String[] args) {
    Resource resource = new Resource(); // 實例化資源
    Producer producer = new Producer(resource); // 實例化生產(chǎn)者和消費者類,它們?nèi)〉猛粋€資源
    Comsumer comsumer = new Comsumer(resource);
    Thread threadProducer = new Thread(producer); // 創(chuàng)建1個生產(chǎn)者線程
    Thread threadComsumer = new Thread(comsumer); // 創(chuàng)建1個消費者線程
    threadProducer.start(); // 分別開啟線程
    threadComsumer.start();
  }
}

架子搭好了,就來運行一下,當然會出現(xiàn)錯誤的結(jié)果,如下所示:

Thread-0...生產(chǎn)者..bread1
Thread-0...生產(chǎn)者..bread2
Thread-0...生產(chǎn)者..bread3
Thread-0...生產(chǎn)者..bread4
Thread-0...生產(chǎn)者..bread5
Thread-1...消費者..bread1
Thread-1...消費者..bread6
Thread-1...消費者..bread6
Thread-1...消費者..bread6
Thread-1...消費者..bread6
Thread-1...消費者..bread6
Thread-0...生產(chǎn)者..bread6
Thread-0...生產(chǎn)者..bread7
Thread-1...消費者..bread6
Thread-1...消費者..bread8
Thread-1...消費者..bread8
Thread-1...消費者..bread8
Thread-0...生產(chǎn)者..bread8
Thread-0...生產(chǎn)者..bread9
Thread-0...生產(chǎn)者..bread10

很明顯,出現(xiàn)了線程安全錯誤。這時,就需要“同步”來保證對共享變量的互斥訪問。上面代碼中需要同步的就是Resource資源類中的produce和consume方法,分別使用synchronized來修飾,由于synchronized修飾方法時使用的是“this”鎖,所以同一個類中的所有被修飾的方法用的都是同一個鎖,那么線程一次只能訪問其中一個方法。加鎖后的Resource類方法如下:

class Resource{  // 資源類
  private String productName; // 資源名稱
  private int count = 1;    // 資源編號
  public synchronized void produce(String name){  // 生產(chǎn)資源函數(shù)
    this.productName = name + count;
    count ++;  // 資源編號遞增,用來模擬資源遞增
    System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者.."+this.productName);
  }
  public synchronized void consume() { // 消費資源函數(shù)
    System.out.println(Thread.currentThread().getName()+"...消費者.."+this.productName);    
  }
}

再來跑一次代碼,又出現(xiàn)問題了:

Thread-0...生產(chǎn)者..bread1
Thread-0...生產(chǎn)者..bread2
Thread-0...生產(chǎn)者..bread3
Thread-0...生產(chǎn)者..bread4
Thread-0...生產(chǎn)者..bread5
Thread-0...生產(chǎn)者..bread6
Thread-0...生產(chǎn)者..bread7
Thread-0...生產(chǎn)者..bread8
Thread-0...生產(chǎn)者..bread9
Thread-0...生產(chǎn)者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10
Thread-1...消費者..bread10

雖然沒有了線程安全錯誤,但是問題來了,生產(chǎn)者不停的生產(chǎn),還沒等消費者消費呢,就將后面的資源覆蓋了前面的資源,導致消費者消費不到前面的資源,這樣很容易造成系統(tǒng)資源浪費。理想中的結(jié)果應該是,生產(chǎn)者生產(chǎn)一個,消費者消費一個,和諧運行。對此,java為多線程引入了”等待-喚醒”機制。

二,等待喚醒機制

與線程做同樣的操作不同,不同線程之間的操作需要等待喚醒機制來保證線程間的執(zhí)行順序。生產(chǎn)者和消費者模式中,生產(chǎn)者和消費者是兩類不同的線程, 這兩類中又可以有很多線程來協(xié)同工作。通俗來說就是,系統(tǒng)為資源設置一個標志flag,該標志用來標明資源是否存在,所有的線程執(zhí)行操作前都要判斷資源是否存在。舉例來說,系統(tǒng)初始化后,資源是空的。接下來要執(zhí)行的可能是生產(chǎn)者線程,也可能是消費者線程。如果是消費者線程獲得執(zhí)行權(quán),先判斷資源,此時為空,就會進入阻塞狀態(tài),交出執(zhí)行權(quán),并喚醒其他線程。如果是生產(chǎn)者線程獲得執(zhí)行權(quán),先判斷資源,此時為空,立馬進行生產(chǎn),完了交出執(zhí)行權(quán)并喚醒其他線程。

注意,上面提到了兩點,第一點是標志位flag,也就是等待機制,生產(chǎn)者要判斷系統(tǒng)沒有資源才進行生產(chǎn),不然要等待,消費者要判斷系統(tǒng)有資源才進行消費,不然也要等待。第二點是喚醒機制,不管是生產(chǎn)者還是消費者,它們在生產(chǎn)完或者消費完后,都要執(zhí)行一個喚醒操作。java提供的等待喚醒機制是由java.lang.Object類中的wait()和notify()函數(shù)組來實現(xiàn)的。其中notify()函數(shù)隨機喚醒一個被wait()的線程,而notifyAll()喚醒所有被wait()的線程。很遺憾,并沒有直接喚醒對方線程的函數(shù)。

notify()適用于單生產(chǎn)者和單消費者模式,而notifyAll()適用于多生產(chǎn)者或多消費者模式。

下面來看2個生產(chǎn)者和2個消費者線程處理一個共享變量的代碼示例:

package com.jimmy.ThreadCommunication;
class Resource2{
  private String productName;
  private int count = 1;
  private boolean flag = false; // 資源類增加一個標志位,默認false,也就是沒有資源
  public synchronized void produce(String name){
    while (flag == true) { // 如果flag為true,也就是有資源了,生產(chǎn)者線程就去等待。
      try {
        wait(); // wait函數(shù)拋出的異常只能被截獲
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    this.productName = name + count;
    count ++;
    System.out.println(Thread.currentThread().getName()+"....生產(chǎn)者.."+this.productName);
    flag = true; // 生產(chǎn)完了就將flag修改為true
    notifyAll(); // 然后喚醒其他線程
  }
  public synchronized void consume() {
    while (flag == false) { // 如果flag為false,也就是沒有資源,消費者線程就去等待
      try {        // 判斷flag要用while,因為線程被喚醒后會再次判斷flag   
        wait();     // 而如果是if來判斷,被喚醒后不會再判斷flag,那么多個生產(chǎn)者線程就可能死鎖
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    System.out.println(Thread.currentThread().getName()+"...消費者.."+this.productName);    
    flag = false; // 消費完了就把標志改為false
    notifyAll();  // 然后喚醒其他線程,因為有多個生產(chǎn)者和消費者線程,所以要用notifyAll,
            // 因為notify只喚醒一個,喚醒到同類型的線程就不好了。
  }
}
class Producer2 implements Runnable{
  private Resource2 res;
  //生產(chǎn)者初始化就要分配資源
  public Producer2(Resource2 res) {
    this.res = res;
  }
  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      res.produce("bread");
    }
  }
}
class Comsumer2 implements Runnable{
  private Resource2 res;
  //同理,消費者一初始化也要分配資源
  public Comsumer2(Resource2 res) {
    this.res = res;
  }
  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      res.consume();
    }
  }
}
public class ProducerAndConsumer2 {
  public static void main(String[] args) {
    Resource2 resource = new Resource2(); // 實例化資源
    Producer2 producer = new Producer2(resource); // 實例化生產(chǎn)者,并傳入資源對象
    Comsumer2 comsumer = new Comsumer2(resource); // 實例化消費者,并傳入相同的資源對象
    Thread threadProducer1 = new Thread(producer); // 創(chuàng)建2個生產(chǎn)者線程
    Thread threadProducer2 = new Thread(producer);
    Thread threadComsumer1 = new Thread(comsumer); // 創(chuàng)建2個消費者線程
    Thread threadComsumer2 = new Thread(comsumer);
    threadProducer1.start();
    threadProducer2.start();
    threadComsumer1.start();
    threadComsumer2.start();
  }
}

上述代碼的輸出結(jié)果如下,是理想中的生產(chǎn)一個,消費一個依次進行。

Thread-0....生產(chǎn)者..bread1
Thread-3...消費者..bread1
Thread-1....生產(chǎn)者..bread2
Thread-2...消費者..bread2
Thread-1....生產(chǎn)者..bread3
Thread-3...消費者..bread3
Thread-0....生產(chǎn)者..bread4
Thread-3...消費者..bread4
Thread-1....生產(chǎn)者..bread5
Thread-2...消費者..bread5
Thread-1....生產(chǎn)者..bread6
Thread-3...消費者..bread6
Thread-0....生產(chǎn)者..bread7
Thread-3...消費者..bread7
Thread-1....生產(chǎn)者..bread8
Thread-2...消費者..bread8
Thread-0....生產(chǎn)者..bread9
Thread-3...消費者..bread9
Thread-0....生產(chǎn)者..bread10
Thread-2...消費者..bread10

可以看出,線程0和1是生產(chǎn)者線程,他們每次只有一個進行生產(chǎn)。線程2和3是消費者線程,同樣的,每次只有一個進行消費。
注意,上述代碼中的問題有2點需要注意,第一點是用if還是while來判斷flag,第二點是用notify還是notifyAll函數(shù)。統(tǒng)一來說,while判斷在線程喚醒后還會再次判斷,如果只有一個生產(chǎn)者和消費者線程的話可以用if,如果有多個生產(chǎn)者或者消費者,就必須用while判斷,不然會出現(xiàn)死鎖。所以,最終要用while和notifyAll()的組合。

總結(jié)

多線程編程往往是多個線程執(zhí)行不同的任務,不同的任務不僅需要“同步”,還需要“等待喚醒機制”。兩者結(jié)合就可以實現(xiàn)多線程編程,其中的生產(chǎn)者消費者模式就是經(jīng)典范例。

然而,使用synchronized修飾同步函數(shù)和使用Object類中的wait,notify方法實現(xiàn)等待喚醒是有弊端的。就是效率問題,notifyAll方法喚醒所有被wait的線程,包括本類型的線程,如果本類型的線程被喚醒,還要再次判斷并進入wait,這就產(chǎn)生了很大的效率問題。理想狀態(tài)下,生產(chǎn)者線程要喚醒消費者線程,而消費者線程要喚醒生產(chǎn)者線程。為此,jdk1.5引入了java.util.concurrent.locks包,并提供了Lock和Condition接口及實現(xiàn)類。

以上就是本文關于Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:Java編程之多線程死鎖與線程間通信簡單實現(xiàn)代碼Java多線程編程小實例模擬停車場系統(tǒng)等,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

相關文章

  • 超個性修改SpringBoot項目的啟動banner的方法

    超個性修改SpringBoot項目的啟動banner的方法

    這篇文章主要介紹了超個性修改SpringBoot項目的啟動banner的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • spring項目如何配置多數(shù)據(jù)源(已上生產(chǎn),親測有效)

    spring項目如何配置多數(shù)據(jù)源(已上生產(chǎn),親測有效)

    這篇文章主要介紹了spring項目如何配置多數(shù)據(jù)源(已上生產(chǎn),親測有效),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • JDK(免安裝)各種版本下載及配置詳細圖文教程

    JDK(免安裝)各種版本下載及配置詳細圖文教程

    這篇文章主要給大家介紹了關于JDK(免安裝)各種版本下載及配置的相關資料,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-07-07
  • Java設計模式之裝飾模式詳解

    Java設計模式之裝飾模式詳解

    這篇文章主要介紹了Java設計模式中的裝飾者模式,裝飾者模式即Decorator?Pattern,裝飾模式是在不必改變原類文件和使用繼承的情況下,動態(tài)地擴展一個對象的功能,裝飾模式又名包裝模式。裝飾器模式以對客戶端透明的方式拓展對象的功能,是繼承關系的一種替代方案
    2022-07-07
  • 使用spring-cache一行代碼解決緩存擊穿問題

    使用spring-cache一行代碼解決緩存擊穿問題

    本文主要介紹了使用spring-cache一行代碼解決緩存擊穿問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-04-04
  • 淺談java中的路徑表示

    淺談java中的路徑表示

    下面小編就為大家?guī)硪黄獪\談java中的路徑表示。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • 詳解Java設計模式編程中的中介者模式

    詳解Java設計模式編程中的中介者模式

    這篇文章主要介紹了Java設計模式編程中的中介者模式,文中舉了典型的同事類與中介者類的例子來解釋說明,需要的朋友可以參考下
    2016-02-02
  • SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼

    SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼

    這篇文章主要介紹了SpringBoot 配合 SpringSecurity 實現(xiàn)自動登錄功能的代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • Spring Boot學習入門之統(tǒng)一異常處理詳解

    Spring Boot學習入門之統(tǒng)一異常處理詳解

    我們在做Web應用的時候,請求處理過程中發(fā)生錯誤是非常常見的情況。下面這篇文章主要給大家介紹了關于Spring Boot學習入門之統(tǒng)一異常處理的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下。
    2017-09-09
  • Java中如何使用?byte?數(shù)組作為?Map?的?key

    Java中如何使用?byte?數(shù)組作為?Map?的?key

    本文將討論在使用HashMap時,當byte數(shù)組作為key時所遇到的問題及其解決方案,介紹使用String和List這兩種數(shù)據(jù)結(jié)構(gòu)作為臨時解決方案的方法,感興趣的朋友跟隨小編一起看看吧
    2023-06-06

最新評論