目錄:
一、線程間的通信示例 返目錄回
多個線程在處理同一資源,任務(wù)卻不同。
假設(shè)有一堆貨物,有一輛車把這批貨物往倉庫里面運,另外一輛車把前一輛車運進(jìn)倉庫的貨物往外面運。這里貨物就是同一資源,但是兩輛車的任務(wù)卻不同,一個是往里運,一個是往外運。
下面舉例子來逐步展示線程間通信:首先建立一個Person類。包含 name 和 sex 屬性, 我們建立一個線程輸入一個對象(即輸入一個name和sex), 另一個線程輸出該對象(即輸出該對象的name 和 sex).
1 package thread.demo; 2 3 class Person 4 { 5 PRivate String name; 6 private String sex; 7 8 public String getName() 9 {10 return name;11 }12 public void setName(String name) 13 {14 this.name = name;15 }16 public String getSex() 17 {18 return sex;19 }20 public void setSex(String sex) 21 {22 this.sex = sex;23 }24 25 }26 //輸入27 class Input implements Runnable28 {29 private Person r;30 31 Input(Person r)32 {33 this.r = r;34 }35 36 public void run()37 {38 int x = 0;39 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象40 {41 if (x == 0)42 {43 r.setName("Mike");44 r.setSex("Male");45 }46 else47 {48 r.setName("Lucy");49 r.setSex("Female");50 }51 x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象52 }53 }54 }55 56 class Output implements Runnable57 {58 private Person r;59 60 Output(Person r)61 {62 this.r = r;63 }64 65 public void run()66 {67 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象68 {69 System.out.println(r.getName() + "..." + r.getSex());70 }71 }72 }73 public class MultithreadDemo_1 74 {75 76 /**77 * @param args78 */79 public static void main(String[] args) 80 {81 //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r82 Person r = new Person();83 Input in = new Input(r);84 Output out = new Output(r);85 86 //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)87 Thread t1 = new Thread(in);88 Thread t2 = new Thread(out);89 90 //開啟線程91 t1.start();92 t2.start();93 }94 }View Code
執(zhí)行結(jié)果就是一直不斷輸出,會發(fā)現(xiàn)有如下類似現(xiàn)象:
問題來了,程序中明明Mike的sex是Male,Lucy是Female,卻出現(xiàn)了上面圖片中“詭異”的的現(xiàn)象,這當(dāng)然是線程安全問題!來分析一下:
在上一篇博文Java多線程技術(shù)學(xué)習(xí)筆記(一)中分析了線程安全問題產(chǎn)生的原因:
回頭看我們的代碼,共享數(shù)據(jù)Person r被兩個線程操作,滿足第一條;操作 r 的代碼就是run方法里面的代碼,看到第36行開始的run方法確實有很多條,滿足第二個條件!所以出現(xiàn)上述詭異輸出其實是很正常的現(xiàn)象!具體到上述代碼,造成原因:
分析了原因,就來解決問題,那就是前一篇博文筆記里面說的同步:
1 //輸入 2 class Input implements Runnable 3 { 4 private Person r; 5 6 Input(Person r) 7 { 8 this.r = r; 9 }10 11 public void run()12 {13 int x = 0;14 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象15 {16 synchronized(r)//同步代碼塊的鎖可以使用任意對象,只要保證多個線程使用的是同一個鎖即可17 {18 if (x == 0)19 {20 r.setName("Mike");21 r.setSex("Male");22 }23 else24 {25 r.setName("Lucy");26 r.setSex("Female");27 }28 }29 x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象30 }31 }32 }33 34 class Output implements Runnable35 {36 private Person r;37 38 Output(Person r)39 {40 this.r = r;41 }42 43 public void run()44 {45 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象46 {47 synchronized(r)48 {49 System.out.println(r.getName() + "..." + r.getSex());50 }51 }52 }53 }View Code
多次運行之后可以驗證,輸出正常:
但是注意到,輸出是連續(xù)一堆Lucy...Female,然后連續(xù)一堆Mike...Male,原因很簡單:一旦切換到輸出線程,該線程不可能只執(zhí)行一次,一下輸出多次,因為name 和 sex 由于同步的緣故,要么是Lucy...Female,要么是Mike...Male,一輸出就是一片相同的Lucy或者M(jìn)ike. 為了展示多線程間的通信,現(xiàn)在要實現(xiàn)的是,輸入線程輸入一個name和sex,就立馬在輸出線程輸出,然后再輸入一個,再輸出一個,如此交替!注意輸入和輸出是在不同線程里面執(zhí)行的!所以就需要線程間通信,即輸入線程輸了一個name和sex,就不在繼續(xù)輸入,而是去通知輸出線程輸出一下剛才輸入的name和sex,輸出一次之后,也不再繼續(xù)輸出,而是去通知輸入線程繼續(xù)輸入新的內(nèi)容,輸入線程和輸出線程如此交替... 這就是所謂的“等待喚醒機制”。
二、等待喚醒機制返目錄回
要達(dá)到上面所說的輸入和輸出線程交替執(zhí)行,需要設(shè)置一個標(biāo)志位,根據(jù)標(biāo)志位來判斷到底是該執(zhí)行輸出還是輸出!
涉及的方法:
翻譯過來意思就是:該方法會導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用了此線程的notify或者notifyAll方法。 注意到wait方法會拋出異常,所以在面我們的代碼中加入了try/catch
這些方法都必須定義在同步中。因為這些方法是用于操作線程狀態(tài)的方法,所以必須要明確到底操作的是哪個鎖上的線程。
注意到上述操作線程的方法都是放在Object類中,這是因為方法都是同步鎖的方法。而鎖可以是任意對象,任意的對象都可以調(diào)用的方法一定定義在Object類中。
代碼思路:初始化標(biāo)志位-->輸入線程輸入-->更改標(biāo)志位-->喚醒輸出線程-->輸出線程輸出-->更該標(biāo)志位-->喚醒輸入線程-->輸入線程輸入--> ...
代碼改動如下:
1 package thread.demo; 2 /* 3 * 等待/喚醒機制 4 */ 5 class Person 6 { 7 private String name; 8 private String sex; 9 boolean full = false;//標(biāo)志位,代表著是否已經(jīng)更新了name和sex 10 11 public String getName() 12 { 13 return name; 14 } 15 public void setName(String name) 16 { 17 this.name = name; 18 } 19 public String getSex() 20 { 21 return sex; 22 } 23 public void setSex(String sex) 24 { 25 this.sex = sex; 26 } 27 28 } 29 //輸入 30 class Input implements Runnable 31 { 32 private Person r; 33 34 Input(Person r) 35 { 36 this.r = r; 37 } 38 39 public void run() 40 { 41 int x = 0; 42 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 43 { 44 synchronized(r)//同步代碼塊的鎖可以使用任意對象,只要保證多個線程使用的是同一個鎖即可 45 { 46 if (r.full) 47 { 48 // 如果full標(biāo)志位為真 49 // r鎖的wait方法讓線程凍結(jié),在線程池中等待,就不執(zhí)行后面的輸入name和sex的語句 50 try { 51 r.wait();//注意調(diào)用wait方法要明確鎖 52 } catch (InterruptedException e) { 53 e.printStackTrace(); 54 } 55 } 56 //如果標(biāo)志位為假,就執(zhí)行下面語句輸入name和sex 57 if (x == 0) 58 { 59 r.setName("Mike"); 60 r.setSex("Male"); 61 } 62 else 63 { 64 r.setName("Lucy"); 65 r.setSex("Female"); 66 } 67 //輸入了一個對象,即一對name和sex之后,將標(biāo)志位置為真 68 r.full = true; 69 //然后通知輸出線程(即喚醒輸出線程)來輸出剛輸入的內(nèi)容 70 r.notify(); 71 } 72 x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象 73 } 74 } 75 } 76 77 class Output implements Runnable 78 { 79 private Person r; 80 81 Output(Person r) 82 { 83 this.r = r; 84 } 85 86 public void run() 87 { 88 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 89 { 90 synchronized(r) 91 { 92 //如果輸入線程還沒有輸入內(nèi)容,輸出線程就等待 93 if (!r.full) 94 { 95 try { 96 r.wait(); 97 } catch (InterruptedException e) { 98 e.printStackTrace(); 99 }100 }101 //如果已經(jīng)輸入了內(nèi)容,就直接輸出102 System.out.println(r.getName() + "..." + r.getSex());103 //輸出完了之后,將標(biāo)志位置為false,表明剛才的內(nèi)容應(yīng)經(jīng)輸出了104 r.full = false;105 //然后通知輸入線程再輸入新內(nèi)容106 r.notify();107 }108 }109 }110 }111 public class MultithreadDemo_1 112 {113 114 /**115 * @param args116 */117 public static void main(String[] args) 118 {119 //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r120 Person r = new Person();121 Input in = new Input(r);122 Output out = new Output(r);123 124 //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)125 Thread t1 = new Thread(in);126 Thread t2 = new Thread(out);127 128 //開啟線程129 t1.start();130 t2.start();131 }132 }View Code
運行結(jié)果:
達(dá)到了預(yù)期。
三、等待喚醒機制的優(yōu)化返目錄回
再考慮上面寫的代碼,其實并不好,同步的目的是為了防止某個線程對name賦值以后,還沒來得及對sex賦值時,其他線程就切了進(jìn)來!所以需要同步的代碼就是賦值的兩行:
59,60行代碼與64,65行代碼代碼功能重復(fù),所以優(yōu)化代碼如下:
1 package thread.demo; 2 /* 3 * 等待/喚醒機制 4 */ 5 class Person 6 { 7 private String name; 8 private String sex; 9 private boolean full = false;//標(biāo)志位,代表著是否已經(jīng)更新了name和sex 10 11 public String getName() 12 { 13 return name; 14 } 15 public void setName(String name) 16 { 17 this.name = name; 18 } 19 public String getSex() 20 { 21 return sex; 22 } 23 public void setSex(String sex) 24 { 25 this.sex = sex; 26 } 27 28 public synchronized void set(String name, String sex) 29 { 30 if (full) 31 { 32 try 33 { 34 this.wait(); //注意同步函數(shù)的鎖是this,所以這里調(diào)用this的wait方法 35 } 36 catch (InterruptedException e) 37 { 38 e.printStackTrace(); 39 } 40 } 41 42 this.name = name; 43 this.sex = sex; 44 full = true; 45 notify(); 46 } 47 48 public synchronized void show() 49 { 50 if (!full) 51 { 52 try 53 { 54 this.wait(); //注意同步函數(shù)的鎖是this,所以這里調(diào)用this的wait方法 55 } 56 catch (InterruptedException e) 57 { 58 e.printStackTrace(); 59 } 60 } 61 //如果已經(jīng)輸入了內(nèi)容,就直接輸出 62 System.out.println(name + "..." + sex); 63 full = false; 64 notify(); 65 } 66 } 67 //輸入 68 class Input implements Runnable 69 { 70 private Person r; 71 72 Input(Person r) 73 { 74 this.r = r; 75 } 76 77 public void run() 78 { 79 int x = 0; 80 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 81 { 82 if (x == 0) 83 { 84 r.set("Mike", "Male"); 85 } 86 else 87 { 88 r.set("Lucy", "Female"); 89 } 90 x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象 91 } 92 } 93 } 94 95 class Output implements Runnable 96 { 97 private Person r; 98 99 Output(Person r)100 {101 this.r = r;102 }103 104 public void run()105 {106 while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 107 {108 r.show();109 }110 }111 }112 public class MultithreadDemo_1 113 {114 115 /**116 * @param args117 */118 public static void main(String[] args) 119 {120 //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r121 Person r = new Person();122 Input in = new Input(r);123 Output out = new Output(r);124 125 //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)126 Thread t1 = new Thread(in);127 Thread t2 = new Thread(out);128 129 //開啟線程130 t1.start();131 t2.start();132 }133 }View Code
功能與前面代碼其實是一樣的。
四、線程間通信經(jīng)典問題:多生產(chǎn)者多消費者問題返目錄回
這個問題很直接:就是一堆生產(chǎn)者生產(chǎn)產(chǎn)品,同時一堆消費者在消費產(chǎn)品!這一堆生產(chǎn)者和消費者對應(yīng)程序中的多個線程,而產(chǎn)品就對應(yīng)著這一堆線程共同操作的資源或者叫做共享數(shù)據(jù)。
我們想達(dá)到的目的是生產(chǎn)一件商品,消費一件,生產(chǎn)消費彼此交替!
首先來看一個生產(chǎn)者,一個消費者的例子,即生產(chǎn)一個就消費一個:
1 package thread.demo; 2 3 /* 4 * 生產(chǎn)者消費者問題 5 */ 6 class Product 7 { 8 private String name;// 產(chǎn)品名稱 9 private int number = 1; // 產(chǎn)品編號 10 private boolean notEmpty = false; 11 public synchronized void produce(String name) 12 { 13 //如果有產(chǎn)品,可以停止生產(chǎn)一會 14 if (notEmpty) 15 { 16 try 17 { 18 this.wait(); 19 } 20 catch (InterruptedException e) 21 { 22 e.printStackTrace(); 23 } 24 } 25 // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26 // 生產(chǎn)的產(chǎn)品名稱 27 this.name = name + number; 28 // 編號遞增 29 number++; 30 // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31 System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32 // 生產(chǎn)完了以后,就有了產(chǎn)品 33 notEmpty = true; 34 //通知消費者來消費 35 notify(); 36 } 37 38 public synchronized void consume() 39 { 40 // 如果沒有產(chǎn)品,無法消費,等待 41 if (!notEmpty) 42 { 43 try 44 { 45 this.wait(); 46 } 47 catch (InterruptedException e) 48 { 49 e.printStackTrace(); 50 } 51 } 52 //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 53 System.out.println(Thread.currentThread().getName() + "消費了:-> " + this.name); 54 //消費完了,通知生產(chǎn)者 55 notEmpty = false; 56 notify(); 57 } 58 } 59 60 // 創(chuàng)建生產(chǎn)者線程 61 class Producer implements Runnable 62 { 63 private Product p; 64 Producer(Product p) 65 { 66 this.p = p; 67 } 68 69 public void run() 70 { 71 while (true) 72 { 73 p.produce("bread"); // 假如生產(chǎn)面包 74 } 75 } 76 77 } 78 79 //創(chuàng)建消費者線程 80 class Consumer implements Runnable 81 { 82 private Product p; 83 Consumer(Product p) 84 { 85 this.p = p; 86 } 87 88 public void run() 89 { 90 while (true)//消費者消費 91 { 92 p.consume(); 93 } 94 } 95 96 } 97 public class ProducerConsumerDemo { 98 99 public static void main(String[] args) 100 {101 // 創(chuàng)建共享資源102 Product p = new Product();103 // 創(chuàng)建兩個線程:生產(chǎn)和消費104 Producer producer = new Producer(p);105 Consumer consumer = new Consumer(p);106 Thread t1 = new Thread(producer);107 Thread t2 = new Thread(consumer);108 t1.start();109 t2.start();110 }111 }View Code
運行結(jié)果:
這其實就是前面等待喚醒機制的另一種展示!下面在此代碼的基礎(chǔ)上改成多生產(chǎn)者,多消費者的示例:
1 public class ProducerConsumerDemo { 2 3 public static void main(String[] args) 4 { 5 // 創(chuàng)建共享資源 6 Product p = new Product(); 7 // 兩個生產(chǎn)者,兩個消費者 8 Producer producer = new Producer(p); 9 Consumer consumer = new Consumer(p);10 Thread t0 = new Thread(producer);11 Thread t1 = new Thread(producer);12 13 Thread t2 = new Thread(consumer);14 Thread t3 = new Thread(consumer);15 16 t0.start();17 t1.start();18 t2.start();19 t3.start();20 }21 }View Code
多次運行,會出現(xiàn)下面類似結(jié)果:
產(chǎn)生的問題:
顯然這些問題都是不合理的,問題肯定出在多線程上,下面分分析。為了方便敘述,代碼全部整理如下:
1 package thread.demo; 2 3 /* 4 * 生產(chǎn)者消費者問題 5 */ 6 class Product 7 { 8 private String name;// 產(chǎn)品名稱 9 private int number = 1; // 產(chǎn)品編號 10 private boolean notEmpty = false; 11 public synchronized void produce(String name) 12 { 13 //如果有產(chǎn)品,可以停止生產(chǎn)一會 14 if (notEmpty) 15 { 16 try 17 { 18 this.wait(); 19 } 20 catch (InterruptedException e) 21 { 22 e.printStackTrace(); 23 } 24 } 25 // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26 // 生產(chǎn)的產(chǎn)品名稱 27 this.name = name + number; 28 // 編號遞增 29 number++; 30 // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31 System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32 // 生產(chǎn)完了以后,就有了產(chǎn)品 33 notEmpty = true; 34 //通知消費者來消費 35 notify(); 36 } 37 38 public synchronized void consume() 39 { 40 // 如果沒有產(chǎn)品,無法消費,等待 41 if (!notEmpty) 42 { 43 try 44 { 45 this.wait(); 46 } 47 catch (InterruptedException e) 48 { 49 e.printStackTrace(); 50 } 51 } 52 //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 53 System.out.println(Thread.currentThread().getName() + "消費了:-> " + this.name); 54 //消費完了,通知生產(chǎn)者 55 notEmpty = false; 56 notify(); 57 } 58 } 59 60 // 創(chuàng)建生產(chǎn)者線程 61 class Producer implements Runnable 62 { 63 private Product p; 64 Producer(Product p) 65 { 66 this.p = p; 67 } 68 69 public void run() 70 { 71 while (true) 72 { 73 p.produce("bread"); // 假如生產(chǎn)面包 74 } 75 } 76 77 } 78 79 //創(chuàng)建消費者線程 80 class Consumer implements Runnable 81 { 82 private Product p; 83 Consumer(Product p) 84 { 85 this.p = p; 86 } 87 88 public void run() 89 { 90 while (true)//消費者消費 91 { 92 p.consume(); 93 } 94 } 95 96 } 97 public class ProducerConsumerDemo { 98 99 public static void main(String[] args) 100 {101 // 創(chuàng)建共享資源102 Product p = new Product();103 // 兩個生產(chǎn)者,兩個消費者104 Producer producer = new Producer(p);105 Consumer consumer = new Consumer(p);106 Thread t0 = new Thread(producer);107 Thread t1 = new Thread(producer);108 109 Thread t2 = new Thread(consumer);110 Thread t3 = new Thread(consumer);111 112 t0.start();113 t1.start();114 t2.start();115 t3.start();116 }117 }View Code
按照上面分析,是不會出現(xiàn)上述運行現(xiàn)象的,于是進(jìn)一步分析:
把程序中的四個線程畫圖分析如下:
其中雙向箭頭表示所連接的兩線程可以互相喚醒。假如存在A箭頭或者B箭頭連續(xù)執(zhí)行的情況,就會出現(xiàn)連續(xù)生產(chǎn)多個產(chǎn)品而不消費的情況,或者連續(xù)消費同一個產(chǎn)品而不生產(chǎn)的情況。很顯然只要發(fā)生中間四個箭頭的情況,就會生產(chǎn)一個,消費一個,從而滿足我們的目的。所以解決的原因顯而易見:防止A和B情況的發(fā)生,即生產(chǎn)者線程不能喚醒生產(chǎn)者線程,只能喚醒消費者線程,而消費者線程也只允許喚醒生產(chǎn)者線程。
五、多生產(chǎn)多消費問題的解決返目錄回
上面分析到,t0喚醒t1后,由于t1從wait處醒過來不判斷標(biāo)記就繼續(xù)往下執(zhí)行,就出現(xiàn)了多生產(chǎn),試想如果t1在被喚醒之后判斷一下標(biāo)記,t1會再次等待,即使t0再次過來也再次判斷標(biāo)記,也會一直等待,而不會去連續(xù)多次生產(chǎn)了,所以把14行和41行的 if 改為while,這樣,每一個線程被喚醒之后就必須重新判斷標(biāo)記,改動之后運行結(jié)果如下:
現(xiàn)象就是運行若干次程序停止了,即就是在Java多線程技術(shù)學(xué)習(xí)筆記(一)提到的死鎖現(xiàn)象,分析原因如下:
雖然線程每次都重新判斷了標(biāo)記,但是會出現(xiàn)上面死鎖的現(xiàn)象,考慮到上面說到notifyAll方法還沒有出場過,試著把notify改為notifyAll:
1 package thread.demo; 2 3 /* 4 * 生產(chǎn)者消費者問題 5 */ 6 class Product 7 { 8 private String name;// 產(chǎn)品名稱 9 private int number = 1; // 產(chǎn)品編號 10 private boolean notEmpty = false; 11 public synchronized void produce(String name) 12 { 13 //如果有產(chǎn)品,可以停止生產(chǎn)一會 14 while (notEmpty) 15 { 16 try 17 { 18 this.wait(); 19 } 20 catch (InterruptedException e) 21 { 22 e.printStackTrace(); 23 } 24 } 25 // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26 // 生產(chǎn)的產(chǎn)品名稱 27 this.name = name + number; 28 // 編號遞增 29 number++; 30 // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31 System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32 // 生產(chǎn)完了以后,就有了產(chǎn)品 33 notEmpty = true; 34 //通知其他線程 35 //notify(); 36 notifyAll(); 37 38 } 39 40 public synchronized void consume() 41 { 42 // 如果沒有產(chǎn)品,無法消費,等待 43 while (!notEmpty) 44 { 45 try 46 { 47 this.wait(); 48 } 49 catch (InterruptedException e) 50 { 51 e.printStackTrace(); 52 } 53 } 54 //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 55 System.out.println(Thread.currentThread().getName() + "消費了:-> " + this.name); 56 //消費完了,通知其他線程 57 notEmpty = false; 58 //notify(); 59 notifyAll(); 60 } 61 } 62 63 // 創(chuàng)建生產(chǎn)者線程 64 class Producer implements Runnable 65 { 66 private Product p; 67 Producer(Product p) 68 { 69 this.p = p; 70 } 71 72 public void run() 73 { 74 while (true) 75 { 76 p.produce("bread"); // 假如生產(chǎn)面包 77 } 78 } 79 80 } 81 82 //創(chuàng)建消費者線程 83 class Consumer implements Runnable 84 { 85 private Product p; 86 Consumer(Product p) 87 { 88 this.p = p; 89 } 90 91 public void run() 92 { 93 while (true)//消費者消費 94 { 95 p.consume(); 96 } 97 } 98 99 }100 public class ProducerConsumerDemo {101 102 public static void main(String[] args) 103 {104 // 創(chuàng)建共享資源105 Product p = new Product();106 // 兩個生產(chǎn)者,兩個消費者107 Producer producer = new Producer(p);108 Consumer consumer = new Consumer(p);109 Thread t0 = new Thread(producer);110 Thread t1 = new Thread(producer);111 112 Thread t2 = new Thread(consumer);113 Thread t3 = new Thread(consumer);114 115 t0.start();116 t1.start();117 t2.start();118 t3.start();119 }120 }View Code
多次運行會發(fā)現(xiàn),結(jié)果正是我們最初想要的:生產(chǎn)一個就消費一個!
原因很簡單:notifyAll會喚醒線程池中所有的線程,假如t0生產(chǎn)了一次,就會喚醒t1,t2,t3,如果t1搶到cpu執(zhí)行權(quán)就會判斷標(biāo)記等待,然后醒著的消費線程搶到執(zhí)行權(quán), 就去消費一次,然后喚醒所有等待的線程,同樣,因為消費了一次,只要消費線程搶到cpu執(zhí)行權(quán)就會根據(jù)標(biāo)記去等待,生產(chǎn)者線程搶到cpu執(zhí)行權(quán)就會判斷標(biāo)記,然后去生產(chǎn),如此循環(huán)!至此,問題得到解決!
六、JDK1.5之后的新加鎖方式返目錄回
在API文檔中有一個Lock接口:
翻譯:Lock 實現(xiàn)提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。
方法如下:
lock方法獲取鎖,unlock方法釋放鎖。
以前使用同步代碼塊和一個對象相結(jié)合的方式,實現(xiàn)線程同步,有了Lock接口以后,可以通過一個鎖對象完成線程的同步。
使用Lock接口的一個已知實現(xiàn)類ReentrantLock來改寫上面的多生產(chǎn)多消費程序,首先看看ReentrantLock的API文檔里寫的怎么用這個類:
而Lock接口的描述里面還提到:Lock 可以支持多個相關(guān)的 Condition 對象,Condition的API:
翻譯:Condition將Object鎖的監(jiān)視器方法:wait,notify和notifyAll分解成截然不同的對象,以便通過將這些對象與任意Lock實現(xiàn)組合,為每個對象提供多個等待set.其中,Lock替代了synchronized方法和語句的使用,Condition替代了Object監(jiān)視器方法的使用。
大致意思就是把這些鎖的方法wait,notify和notifyAll封裝在Condition中,而鎖Lock和Condition是什么關(guān)系呢?在上面Lock方法的截圖中:
即newCondition方法返回綁定到此Lock實例的新 Condition實例,所以Lock和Condition就是通過這個方法綁定一起,然后就能通過Condition實例調(diào)用與該鎖想關(guān)的wait,notify和notifyAll方法。
但是注意wait,notify和notifyAll方法在Condition中的名稱有所改變,但是功能是一樣的:
好了,根據(jù)上面的知識,得出修改的代碼:
1 package thread.demo; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /* 8 * 生產(chǎn)者消費者問題 9 */ 10 class NewProduct 11 { 12 private String name;// 產(chǎn)品名稱 13 private int number = 1; // 產(chǎn)品編號 14 private boolean notEmpty = false; 15 16 // 創(chuàng)建一個鎖對象 17 Lock lock = new ReentrantLock(); 18 19 // 通過已有的鎖獲取該鎖上的監(jiān)視器對象 20 Condition c = lock.newCondition(); 21 public void produce(String name) 22 { 23 lock.lock(); 24 try 25 { 26 //如果有產(chǎn)品,可以停止生產(chǎn)一會 27 while (notEmpty) 28 { 29 /* 30 try 31 { 32 this.wait(); 33 } 34 catch (InterruptedException e) 35 { 36 e.printStackTrace(); 37 } 38 */ 39 40 try { 41 c.await(); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 } 45 } 46 // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 47 // 生產(chǎn)的產(chǎn)品名稱 48 this.name = name + number; 49 // 編號遞增 50 number++; 51 // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 52 System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 53 // 生產(chǎn)完了以后,就有了產(chǎn)品 54 notEmpty = true; 55 //通知其他線程 56 //notify(); 57 //notifyAll(); 58 c.signalAll(); 59 } 60 finally 61 { 62 lock.unlock(); 63 } 64 } 65 66 public void consume() 67 { 68 lock.lock(); 69 try 70 { 71 // 如果沒有產(chǎn)品,無法消費,等待 72 while (!notEmpty) 73 { 74 /* 75 try 76 { 77 this.wait(); 78 } 79 catch (InterruptedException e) 80 { 81 e.printStackTrace(); 82 } 83 */ 84 try { 85 c.await(); 86 } catch (InterruptedException e) { 87 e.printStackTrace(); 88 } 89 } 90 //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 91 System.out.println(Thread.currentThread().getName() + "消費了:-> " + this.name); 92 //消費完了,通知其他線程 93 notEmpty = false; 94 //notify(); 95 //notifyAll(); 96 c.signalAll(); 97 } 98 finally 99 {100 lock.unlock();101 }102 }103 }104 105 // 創(chuàng)建生產(chǎn)者線程106 class NewProducer implements Runnable107 {108 private NewProduct p;109 NewProducer(NewProduct p2)110 {111 this.p = p2;112 }113 114 public void run()115 {116 while (true)117 {118 p.produce("bread"); // 假如生產(chǎn)面包119 }120 }121 122 }123 124 //創(chuàng)建消費者線程125 class NewConsumer implements Runnable126 {127 private NewProduct p;128 NewConsumer(NewProduct p)129 {130 this.p = p;131 }132 133 public void run()134 {135 while (true)//消費者消費136 {137 p.consume();138 }139 }140 141 }142 public class LockDemo {143 144 public static void main(String[] args) 145 {146 // 創(chuàng)建共享資源147 NewProduct p = new NewProduct();148 // 兩個生產(chǎn)者,兩個消費者149 NewProducer NewProducer = new NewProducer(p);150 NewConsumer NewConsumer = new NewConsumer(p);151 Thread
新聞熱點
疑難解答