注:此示例來自MLDN講師李興華JAVA SE基礎教學部分
生產者和消費者是多線程中一個經典的操作案例下面一起看下代碼:
示例一:
package org.lx.multithreading;
/**
* 定義一個信息類
* @author Solitary
*/
class Info {
private String name = "羅星" ; //定義name屬性
private String content ="JAVA初學者"; //定義content屬性
//getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
} ;
/**
* 定義生產者
* @author Solitary
*/
class Producer implements Runnable { //通過接口Runnable實現多線程
private Info info = null ; //保存Info引用
public Producer(Info info) { //通過構造方法傳遞引用
this.info = info ;
}
@Override
public void run() {
boolean flag = false ; //定義標志位
for(int i = 0; i < 50; i++) { //生產50次信息
if(flag){ //如果標志位為true 將設置 中文內容
this.info.setName("小星") ; //設置名字
try {
//為了更好的體現代碼運行效果在設置姓名和內容之間加入延遲操作
Thread.sleep(300) ;
} catch (InterruptedException e) { //線程被打斷后會拋出此異常
e.printStackTrace();
}
this.info.setContent("JAVA初學者") ; //設置內容
flag = false ; //改變標志位,用于變換輸入內容
}else{ //如果標志位為false 將設置英文內容
this.info.setName("Solitary") ; //設置名字
try {
//為了更好的體現代碼運行效果在設置姓名和內容之間加入延遲操作
Thread.sleep(300) ;
} catch (InterruptedException e) { //線程被打斷后會拋出此異常
e.printStackTrace();
}
this.info.setContent("Coder") ; //設置內容
flag = true ; //改變標志位,用于變換輸入內容
}
}
}
} ;
/**
* 定義消費者
* @author Solitary
*/
class Consumer implements Runnable {
private Info info = null ; //用于保存Info引用,其目的是為了讓消費者和生產者擁有同一個info
public Consumer(Info info){
this.info = info ;
}
public void run() {
for(int i = 0; i < 50; i++) { //消費和也從info中取50次消息
try {
Thread.sleep(300) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
} ;
/**
* 測試代碼
* @author Solitary\
*/
public class MultiThreadingDemo01 {
public static void main(String args[]){
Info info = new Info() ; // 實例化Info對象
Producer pro = new Producer(info) ; // 生產者
Consumer con = new Consumer(info) ; // 消費者
new Thread(pro).start() ; //啟動線程
new Thread(con).start() ; //啟動線程
}
}
示例一(執行效果):
小星 --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
Solitary --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> JAVA初學者
小星 --> JAVA初學者
Solitary --> Coder
Solitary --> JAVA初學者
Solitary --> Coder
小星 --> Coder
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
Solitary --> JAVA初學者
小星 --> Coder
小星 --> JAVA初學者
Solitary --> Coder
Solitary --> JAVA初學者
Solitary --> JAVA初學者
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> JAVA初學者
小星 --> JAVA初學者
小星 --> Coder
小星 --> JAVA初學者
Solitary --> JAVA初學者
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> JAVA初學者
小星 --> JAVA初學者
請注意運行結果,為什么我代碼里面明明成對設置的是:小星 --> JAVA初學者; Solitary --> Coder而運行結果確實有不匹配的呢?
分析:
因為生產者和消費者的線程都已啟動,那么不能保證誰在前,或者誰在后,在生產者還在設置內容的時候(比如:已經設置好的Info的name=小星,Context=JAVA初學者,而此時生產者又設置了name = Solitary正打算設置Content = Coder),而消費者已經取走了內容,那么顯示的肯定就是Solitary --> JAVA初學者這樣的結果,因為兩個線程都在這執行著,出現了不匹配的結果。可以將代碼修改為:
示例二
package org.lx.multithreading;
/**
* 定義一個信息類
* @author Solitary
*/
class Info {
private String name = "羅星" ; //定義name屬性
private String content ="JAVA初學者"; //定義content屬性
//getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content ;
}
public synchronized void set(String name, String content){ //由此方法統一設置信息
this.setName(name) ;
try {
/* 此時這個地方加不加延遲沒有任何關系,因為該方法已經同步
在執行到此方法(get())時,此方法將會完整結束后才會執行
到別的方法 ,所以一定會完整設置完信息之后才會輪到信息的讀取方法*/
Thread.sleep(300) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content) ;
}
public synchronized void get(){
try {
Thread.sleep(300) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
}
} ;
/**
* 定義生產者
* @author Solitary
*/
class Producer implements Runnable { //通過接口Runnable實現多線程
private Info info = null ; //保存Info引用
public Producer(Info info) { //通過構造方法傳遞引用
this.info = info ;
}
@Override
public void run() {
boolean flag = false ; //定義標志位
for(int i = 0; i < 50; i++) { //生產50次信息
if(flag){ //如果標志位為true 將設置 中文內容
this.info.set("小星", "JAVA初學者") ;
flag = false ; //改變標志位,用于變換輸入內容
}else{ //如果標志位為false 將設置英文內容
this.info.set("Solitary", "Coder") ;
flag = true ; //改變標志位,用于變換輸入內容
}
}
}
} ;
/**
* 定義消費者
* @author Solitary
*/
class Consumer implements Runnable {
private Info info = null ; //用于保存Info引用,其目的是為了讓消費者和生產者擁有同一個info
public Consumer(Info info){
this.info = info ;
}
public void run() {
for(int i = 0; i < 50; i++) { //消費和也從info中取50次消息
this.info.get() ;
}
}
} ;
/**
* 測試代碼
* @author Solitary\
*/
public class MultiThreadingDemo01 {
public static void main(String args[]){
Info info = new Info() ; // 實例化Info對象
Producer pro = new Producer(info) ; // 生產者
Consumer con = new Consumer(info) ; // 消費者
new Thread(pro).start() ; //啟動線程
new Thread(con).start() ; //啟動線程
}
}
示例二(運行效果)
Solitary --> Coder
Solitary --> Coder
Solitary --> Coder
Solitary --> Coder
Solitary --> Coder
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
Solitary --> Coder
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
小星 --> JAVA初學者
觀察運行結果發現,不匹配的現象已經解決了,因為設置name 與 content 的步驟都在一個同步方法中,所以不會導致設置不完整的情況,但是并沒有達到我們想要的結果,
觀察出現了連續出現重復的內容,這是為什么呢?
分析:這兩個方法在不同的線程中,當一條線程中的方法設置完內容之后,另一個線程取出內容顯示,取完之后當了土財主不讓,繼續取,生產者無法更新里邊的內容,那顯示出來的內容肯定就是重復的,因為多線程中,不能保證這個線程什么時候執行。怎樣解決呢?
1.中間那塊矩形代表信息載體Info實例,載體中沒有產品的時候上面顯示為綠燈,那么這時生產者能放入產品。而消費者不能取出產品。
2.當生產者放入產品之后燈變成紅色,這時候生產者將不能放入產品,而輪到消費者取出產品,那么去完之后再次改變燈為綠色。這樣一直反復執行下去。
那么載體上的那盞燈屬于一個標志位,我們可以在代碼中用boolean表示,那么這盞燈是屬于信息載體Info的標志,因為設置/取出這兩個方法都在Info中定義,生產者與消費者只是負責調用Info之中的方法,就讓Info中的方法判斷一下自身的標志位的狀態判斷是否生產或者取出。
實例三
package org.lx.multithreading ;
class Info //定義信息類
{
private String name = "羅星" ; //定義name屬性
private String content = "JAVA初學者" ; //定義content屬性
private boolean flag = false ; //設置標志位
public synchronized void set(String name, String content){
if(!flag){ //方法每次執行的時候都檢查一下標志位狀態,從而判斷時候進行生產
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
this.setName(name) ; //設置名稱
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 設置內容
flag = false ; //改變標志位,表示可以取走
super.notify() ; //喚醒線程
}
public synchronized void get(){
if(flag){ ////方法每次執行的時候都檢查一下標志位狀態,從而判斷時候進行取出
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
flag = true ; //改變標志位,表示可以生成
super.notify() ;
}
public void setName(String name){
this.name = name ;
}
public String getName(){
return this.name ;
}
public void setContent(String content){
this.content = content ;
}
public String getContent(){
return this.content ;
}
}
class Producer implements Runnable //通過Runnable實現多線程
{
public Info info = null ; //保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; //定義標記位
for(int i = 0; i < 50; i++){
if(flag){
this.info.set("羅星", "JAVA初學者") ;
flag = false ;
}else{
this.info.set("Solitary", "Coder") ;
flag = true ;
}
}
}
} ;
class Consumer implements Runnable{ //消費者類
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i = 0; i < 50; i++){
this.info.get() ;
}
}
} ;
//測試代碼
public class MultiThreading
{
public static void main(String[] args){
Info info = new Info() ; //實例化Info對象
Producer pro = new Producer(info) ; //生產者
Consumer con = new Consumer(info) ; //消費者
new Thread(pro).start() ;
new Thread(con).start() ;
}
}
在此利用了Object類對線程的支持,Object中定義了方法:
public final void
wait() throws
InterruptedException
在其他線程調用此對象的 notify()
方法或 notifyAll()
方法前,導致當前線程等待。
換句話說,此方法的行為就好像它僅執行
wait(0) 調用一樣。
當前線程必須擁有此對象監視器。該線程發布對此監視器的所有權并等待,直到其他線程通過調用 notify
方法,或 notifyAll
方法通知在此對象的監視器上等待的線程醒來。然后該線程將等到重新獲得對監視器的所有權后才能繼續執行。
public final void notify()
- 喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現做出決定時發生。線程通過調用其中一個
wait
方法,在對象的監視器上等待。 直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
當生產者線程調用此方法時,首先檢查一下標志位,判斷是否等待,此時消費者也在調用相應方法判斷是否取出,如果可以取出,那么取出后改變標志位的狀態,然后喚醒該對象中等待的線程,這時狀態改變生產者既進行生產操作。
序言:
小弟是一個JAVA新手,這也是第一次寫博客,此文純屬于學習筆記,以便日后復習,如果前輩們認為描述有錯誤的地方,或者覺得不清晰感覺思路混亂的地方,還請前輩們多多賜教,晚生感激不勝! 謝謝。