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