Java多線程之并發(fā)協(xié)作生產(chǎn)者消費(fèi)者設(shè)計(jì)模式

題圖

兩個(gè)線程一個(gè)生產(chǎn)者個(gè)一個(gè)消費(fèi)者

需求情景

涉及問題

代碼實(shí)現(xiàn)(共三個(gè)類和一個(gè)main方法的測試類)

多個(gè)線程,多個(gè)生產(chǎn)者和多個(gè)消費(fèi)者的問題

需求情景

涉及問題

再次測試代碼

代碼改進(jìn)(Resource中的if->while)

最后代碼改進(jìn)(Resource中的notify()->notifyAll())

————————————————————————————————

兩個(gè)線程一個(gè)生產(chǎn)者個(gè)一個(gè)消費(fèi)者

需求情景

兩個(gè)線程,一個(gè)負(fù)責(zé)生產(chǎn),一個(gè)負(fù)責(zé)消費(fèi),生產(chǎn)者生產(chǎn)一個(gè),消費(fèi)者消費(fèi)一個(gè)

涉及問題

同步問題:如何保證同一資源被多個(gè)線程并發(fā)訪問時(shí)的完整性。常用的同步方法是采用標(biāo)記或加鎖機(jī)制

wait() / nofity() 方法是基類Object的兩個(gè)方法,也就意味著所有Java類都會(huì)擁有這兩個(gè)方法,這樣,我們就可以為任何對象實(shí)現(xiàn)同步機(jī)制。

wait()方法:當(dāng)緩沖區(qū)已滿/空時(shí),生產(chǎn)者/消費(fèi)者線程停止自己的執(zhí)行,放棄鎖,使自己處于等等狀態(tài),讓其他線程執(zhí)行。

notify()方法:當(dāng)生產(chǎn)者/消費(fèi)者向緩沖區(qū)放入/取出一個(gè)產(chǎn)品時(shí),向其他等待的線程發(fā)出可執(zhí)行的通知,同時(shí)放棄鎖,使自己處于等待狀態(tài)。

代碼實(shí)現(xiàn)(共三個(gè)類和一個(gè)main方法的測試類)

Resource.java

/**

* Created by yuandl on 2016-10-11./**

* 資源

*/public class Resource {

/*資源序號*/

private int number = 0;

/*資源標(biāo)記*/

private boolean flag = false;

/**

* 生產(chǎn)資源

*/

public synchronized void create() {

if (flag) {//先判斷標(biāo)記是否已經(jīng)生產(chǎn)了,如果已經(jīng)生產(chǎn),等待消費(fèi);

try {

wait();//讓生產(chǎn)線程等待

} catch (InterruptedException e) {

e.printStackTrace();

}

}

number++;//生產(chǎn)一個(gè)

System.out.println(Thread.currentThread().getName() + "生產(chǎn)者------------" + number);

flag = true;//將資源標(biāo)記為已經(jīng)生產(chǎn)

notify();//喚醒在等待操作資源的線程(隊(duì)列)

}

/**

* 消費(fèi)資源

*/

public synchronized void destroy() {

if (!flag) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName() + "消費(fèi)者****" + number);

flag = false;

notify();

}

}

Producer.java

/**

* Created by yuandl on 2016-10-11.

*

/**

* 生產(chǎn)者

*/public class Producer implements Runnable {

private Resource resource;

public Producer(Resource resource) {

this.resource = resource;

}

@Override

public void run() {

while (true) {

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

resource.create();

}

}

}

Consumer.java

/**

* 消費(fèi)者

*/

public class Consumer implements Runnable {

private Resource resource;

public Consumer(Resource resource) {

this.resource = resource;

}

@Override

public void run() {

while (true) {

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

resource.destroy();

}

}

}

ProducerConsumerTest.java

/**

* Created by yuandl on 2016-10-11.

*/

public class ProducerConsumerTest {

public static void main(String args[]) {

Resource resource = new Resource();

new Thread(new Producer(resource)).start();//生產(chǎn)者線程

new Thread(new Consumer(resource)).start();//消費(fèi)者線程

}

}

打印結(jié)果

Thread-0生產(chǎn)者------------1

Thread-1消費(fèi)者****1

Thread-0生產(chǎn)者------------2

Thread-1消費(fèi)者****2

Thread-0生產(chǎn)者------------3

Thread-1消費(fèi)者****3

Thread-0生產(chǎn)者------------4

Thread-1消費(fèi)者****4

Thread-0生產(chǎn)者------------5

Thread-1消費(fèi)者****5

Thread-0生產(chǎn)者------------6

Thread-1消費(fèi)者****6

Thread-0生產(chǎn)者------------7

Thread-1消費(fèi)者****7

Thread-0生產(chǎn)者------------8

Thread-1消費(fèi)者****8

Thread-0生產(chǎn)者------------9

Thread-1消費(fèi)者****9

Thread-0生產(chǎn)者------------10

Thread-1消費(fèi)者****10

以上打印結(jié)果可以看出沒有任何問題

————————————————————————————————————————

多個(gè)線程,多個(gè)生產(chǎn)者和多個(gè)消費(fèi)者的問題

需求情景

四個(gè)線程,兩個(gè)個(gè)負(fù)責(zé)生產(chǎn),兩個(gè)個(gè)負(fù)責(zé)消費(fèi),生產(chǎn)者生產(chǎn)一個(gè),消費(fèi)者消費(fèi)一個(gè)。

涉及問題

notifyAll()方法:當(dāng)生產(chǎn)者/消費(fèi)者向緩沖區(qū)放入/取出一個(gè)產(chǎn)品時(shí),向其他等待的所有線程發(fā)出可執(zhí)行的通知,同時(shí)放棄鎖,使自己處于等待狀態(tài)。

再次測試代碼

ProducerConsumerTest.java

/**

* Created by yuandl on 2016-10-11.

*/

public class ProducerConsumerTest {

public static void main(String args[]) {

Resource resource = new Resource();

new Thread(new Consumer(resource)).start();//生產(chǎn)者線程

new Thread(new Consumer(resource)).start();//生產(chǎn)者線程

new Thread(new Producer(resource)).start();//消費(fèi)者線程

new Thread(new Producer(resource)).start();//消費(fèi)者線程

}

}

運(yùn)行結(jié)果

Thread-0生產(chǎn)者------------100

Thread-3消費(fèi)者****100

Thread-0生產(chǎn)者------------101

Thread-3消費(fèi)者****101

Thread-2消費(fèi)者****101

Thread-1生產(chǎn)者------------102

Thread-3消費(fèi)者****102

Thread-0生產(chǎn)者------------103

Thread-2消費(fèi)者****103

Thread-1生產(chǎn)者------------104

Thread-3消費(fèi)者****104

Thread-1生產(chǎn)者------------105

Thread-0生產(chǎn)者------------106

Thread-2消費(fèi)者****106

Thread-1生產(chǎn)者------------107

Thread-3消費(fèi)者****107

Thread-0生產(chǎn)者------------108

Thread-2消費(fèi)者****108

Thread-0生產(chǎn)者------------109

Thread-2消費(fèi)者****109

Thread-1生產(chǎn)者------------110

Thread-3消費(fèi)者****110

通過以上打印結(jié)果發(fā)現(xiàn)問題

101生產(chǎn)了一次,消費(fèi)了兩次

105生產(chǎn)了,而沒有消費(fèi)

原因分析

當(dāng)兩個(gè)線程同時(shí)操作生產(chǎn)者生產(chǎn)或者消費(fèi)者消費(fèi)時(shí),如果有生產(chǎn)者或者的兩個(gè)線程都wait()時(shí),再次notify(),由于其中一個(gè)線程已經(jīng)改變了標(biāo)記而另外一個(gè)線程再次往下直接執(zhí)行的時(shí)候沒有判斷標(biāo)記而導(dǎo)致的。

if判斷標(biāo)記,只有一次,會(huì)導(dǎo)致不該運(yùn)行的線程運(yùn)行了。出現(xiàn)了數(shù)據(jù)錯(cuò)誤的情況。

解決方案

while判斷標(biāo)記,解決了線程獲取執(zhí)行權(quán)后,是否要運(yùn)行!也就是每次wait()后再notify()時(shí)先再次判斷標(biāo)記

代碼改進(jìn)(Resource中的if->while)

Resource.java

/**

* Created by yuandl on 2016-10-11./**

* 資源

*/public class Resource {

/*資源序號*/

private int number = 0;

/*資源標(biāo)記*/

private boolean flag = false;

/**

* 生產(chǎn)資源

*/

public synchronized void create() {

while (flag) {//先判斷標(biāo)記是否已經(jīng)生產(chǎn)了,如果已經(jīng)生產(chǎn),等待消費(fèi);

try {

wait();//讓生產(chǎn)線程等待

} catch (InterruptedException e) {

e.printStackTrace();

}

}

number++;//生產(chǎn)一個(gè)

System.out.println(Thread.currentThread().getName() + "生產(chǎn)者------------" + number);

flag = true;//將資源標(biāo)記為已經(jīng)生產(chǎn)

notify();//喚醒在等待操作資源的線程(隊(duì)列)

}

/**

* 消費(fèi)資源

*/

public synchronized void destroy() {

while (!flag) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName() + "消費(fèi)者****" + number);

flag = false;

notify();

}

}

運(yùn)行結(jié)果


再次發(fā)現(xiàn)問題

打印到某個(gè)值比如生產(chǎn)完74,程序運(yùn)行卡死了,好像鎖死了一樣。

原因分析

notify:只能喚醒一個(gè)線程,如果本方喚醒了本方,沒有意義。而且while判斷標(biāo)記+notify會(huì)導(dǎo)致"死鎖"。

解決方案

notifyAll解決了本方線程一定會(huì)喚醒對方線程的問題。

最后代碼改進(jìn)(Resource中的notify()->notifyAll())

Resource.java

/**

* Created by yuandl on 2016-10-11./**

* 資源

*/public class Resource {

/*資源序號*/

private int number = 0;

/*資源標(biāo)記*/

private boolean flag = false;

/**

* 生產(chǎn)資源

*/

public synchronized void create() {

while (flag) {//先判斷標(biāo)記是否已經(jīng)生產(chǎn)了,如果已經(jīng)生產(chǎn),等待消費(fèi);

try {

wait();//讓生產(chǎn)線程等待

} catch (InterruptedException e) {

e.printStackTrace();

}

}

number++;//生產(chǎn)一個(gè)

System.out.println(Thread.currentThread().getName() + "生產(chǎn)者------------" + number);

flag = true;//將資源標(biāo)記為已經(jīng)生產(chǎn)

notifyAll();//喚醒在等待操作資源的線程(隊(duì)列)

}

/**

* 消費(fèi)資源

*/

public synchronized void destroy() {

while (!flag) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(Thread.currentThread().getName() + "消費(fèi)者****" + number);

flag = false;

notifyAll();

}

}

運(yùn)行結(jié)果

Thread-0生產(chǎn)者------------412

Thread-2消費(fèi)者****412

Thread-0生產(chǎn)者------------413

Thread-3消費(fèi)者****413

Thread-1生產(chǎn)者------------414

Thread-2消費(fèi)者****414

Thread-1生產(chǎn)者------------415

Thread-2消費(fèi)者****415

Thread-0生產(chǎn)者------------416

Thread-3消費(fèi)者****416

Thread-1生產(chǎn)者------------417

Thread-3消費(fèi)者****417

Thread-0生產(chǎn)者------------418

Thread-2消費(fèi)者****418

Thread-0生產(chǎn)者------------419

Thread-3消費(fèi)者****419

Thread-1生產(chǎn)者------------420

Thread-2消費(fèi)者****420

以上就大功告成了,沒有任何問題

來源:http://blog.csdn.net/linglongxin24/article/details/52788774

—————————————————————————————————

在閱讀文章過程中如有疑問,請發(fā)布到極樂網(wǎng);或者長按下方二維碼,關(guān)注極官方微信平臺(tái),在平臺(tái)下方留言,我們將第一時(shí)間為您解答。

極樂技術(shù)問答
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容