參考并發(fā)容器-阻塞隊(duì)列 第四部分“阻塞隊(duì)列的實(shí)現(xiàn)原理”。
參考代碼生產(chǎn)者-消費(fèi)者
1.缺少wait會(huì)出現(xiàn)的問題
三個(gè)類:售貨員Clerk,工廠Factory,消費(fèi)者Consumer
Factory和Consumer共享Clerk對(duì)象
class Clerk{
//商品數(shù)量默認(rèn)是0,volatile關(guān)鍵字保證內(nèi)存可見性
private volatile int product=0;
//進(jìn)貨,synchronized關(guān)鍵字保證原子性,互斥性
public synchronized void get(){
if(product>10){
System.out.println("貨滿了");
}else {
++product;
System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
}
}
//售貨
public synchronized void sale(){
if(product<=0){
System.out.println("沒貨了");
}else{
System.out.println(Thread.currentThread().getName()+"賣貨"+product);
--product;
}
}
}
class Factory implements Runnable{
private Clerk clerk;
Factory(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for(int i=0;i<20;i++){
clerk.get();//進(jìn)貨
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
for(int i=0;i<20;i++){
clerk.sale();//賣貨
}
}
}
public static void main(String[] args) {
Clerk clerk = new Clerk();
Factory factory = new Factory(clerk);
Consumer consumer = new Consumer(clerk);
Thread tf = new Thread(factory);
Thread tc = new Thread(consumer);
tf.start();
tc.start();
}
輸出結(jié)果:
Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-0進(jìn)貨9
Thread-0進(jìn)貨10
Thread-0進(jìn)貨11
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
貨滿了
Thread-1賣貨11
Thread-1賣貨10
Thread-1賣貨9
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
沒貨了
- 問題:上述的情況是當(dāng)沒貨的時(shí)候還會(huì)繼續(xù)調(diào)用該方法,從而占用資源,二貨滿的情況下也會(huì)重復(fù)調(diào)用進(jìn)貨方法,占用資源,這樣是不合理的。
- 解決方式:當(dāng)貨滿了,應(yīng)該停止進(jìn)貨,釋放鎖讓消費(fèi)者消費(fèi),當(dāng)沒貨了應(yīng)該停止消費(fèi)釋放鎖,讓進(jìn)貨,這是我們想要的邏輯。
使用wait()和notifyAll()這兩個(gè)方法來實(shí)現(xiàn)。
class Clerk{
//商品數(shù)量默認(rèn)是0
private volatile int product=0;
//進(jìn)貨
public synchronized void get(){
if(product>10){
System.out.println("貨滿了");
try {
this.wait();//等待并釋放clerk的對(duì)象鎖,進(jìn)入線程隊(duì)列等待被喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
++product;
System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
notifyAll();//喚醒等待的線程
}
}
//售貨
public synchronized void sale(){
if(product<=0){
System.out.println("沒貨了");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println(Thread.currentThread().getName()+"賣貨"+product);
--product;
notifyAll();
}
}
}
- 結(jié)果
Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-0進(jìn)貨9
Thread-0進(jìn)貨10
Thread-0進(jìn)貨11
貨滿了
Thread-1賣貨11
Thread-1賣貨10
Thread-1賣貨9
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1
沒貨了
Thread-0進(jìn)貨1
Thread-0進(jìn)貨2
Thread-0進(jìn)貨3
Thread-0進(jìn)貨4
Thread-0進(jìn)貨5
Thread-0進(jìn)貨6
Thread-0進(jìn)貨7
Thread-0進(jìn)貨8
Thread-1賣貨8
Thread-1賣貨7
Thread-1賣貨6
Thread-1賣貨5
Thread-1賣貨4
Thread-1賣貨3
Thread-1賣貨2
Thread-1賣貨1
2.線程阻塞無法喚醒
- 當(dāng)產(chǎn)品為1時(shí),生成者線程生產(chǎn)結(jié)束;此時(shí)Consumer還處于wait無法得到喚醒。
這種情景對(duì)嗎?這種問題什么情況下會(huì)發(fā)生? - 解決方式:去掉else,每次都會(huì)喚醒另外一方的線程。
class Clerk{
//商品數(shù)量默認(rèn)是0
private volatile int product=0;
//進(jìn)貨
public synchronized void get(){
if(product>10){
System.out.println("貨滿了");
try {
this.wait();//等待并釋放clerk的對(duì)象鎖,進(jìn)入線程隊(duì)列等待被喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
++product;
System.out.println(Thread.currentThread().getName()+"進(jìn)貨"+product);
notifyAll();//喚醒等待的線程
}
//售貨
public synchronized void sale(){
if(product<=0){
System.out.println("沒貨了");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"賣貨"+product);
--product;
notifyAll();
}
}
3.虛假喚醒
Clerk clerk = new Clerk();
Factory factory = new Factory(clerk);
Consumer consumer = new Consumer(clerk);
Thread tf = new Thread(factory);
Thread tc = new Thread(consumer);
Thread tc2 = new Thread(consumer);
tf.start();
tc.start();
tc2.start();
- 會(huì)出現(xiàn)負(fù)數(shù)!
沒貨了
沒貨了
Thread-0進(jìn)貨1
Thread-2賣貨1
沒貨了
Thread-1賣貨0
沒貨了
Thread-2賣貨-1
沒貨了
Thread-1賣貨-2
沒貨了
Thread-2賣貨-3
沒貨了
Thread-1賣貨-4
沒貨了
Thread-2賣貨-5
沒貨了
Thread-1賣貨-6
- 原因
兩個(gè)消費(fèi)者都處于wait狀態(tài),然后生產(chǎn)者生產(chǎn)了一個(gè)notifyAll,兩個(gè)消費(fèi)者同時(shí)往下執(zhí)行,導(dǎo)致product為負(fù)數(shù)。 - 解決方法:防止虛假喚醒,應(yīng)該放在循環(huán)中,多次進(jìn)行檢查,直到滿足條件才進(jìn)行下一步
4.守護(hù)線程解決線程阻塞
- 當(dāng)多個(gè)消費(fèi)者和一個(gè)生產(chǎn)者的時(shí)候,生產(chǎn)者有可能先結(jié)束循環(huán),但是消費(fèi)者還沒結(jié)束,結(jié)果到了其他消費(fèi)者的時(shí)候發(fā)現(xiàn)product是小于0的于是就wait,程序一直等待得不到結(jié)束,就會(huì)一直在wait()
- 解決方式
在共享資源clerk類中定義生產(chǎn)者線程標(biāo)志位,在main線程中創(chuàng)建一個(gè)線程設(shè)置為守護(hù)線程 并啟動(dòng),在該守護(hù)線程中創(chuàng)建匿名內(nèi)部類Runnable,并在run方法中判斷生產(chǎn)者線程isAlive() 。如果生產(chǎn)者線程結(jié)束,就把標(biāo)志位置為false,該標(biāo)識(shí)位和消費(fèi)者線程的while判斷條件中串聯(lián),當(dāng)生產(chǎn)者線程為false的之后短路,使得消費(fèi)和線程啥都不做,直到線程結(jié)束。 - Clerk中設(shè)置Factory 線程標(biāo)志位
private boolean facctoryFlg = true;//工廠線程結(jié)束的標(biāo)志位,為false表示線程執(zhí)行完畢
public boolean isFacctoryFlg() {
return facctoryFlg;
}
public void setFacctoryFlg(boolean facctoryFlg) {
this.facctoryFlg = facctoryFlg;
}
- Main中創(chuàng)建守護(hù)線程
//創(chuàng)建守護(hù)線程
Thread daemon = new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(!tf.isAlive()){
clerk.setFacctoryFlg(false);
System.out.println("factory--------------"+tf.isAlive());
break;
}
}
}
});
daemon.setDaemon(true);//設(shè)置為守護(hù)線程(后臺(tái)線程)
daemon.start();
- 修改·Clerk的sale方法
public synchronized void sale(){
while(product<=0){
//當(dāng)Factory線程結(jié)束的時(shí)候,直接結(jié)束sale方法
if(!isFacctoryFlg()){
return;
}
System.out.println("沒貨了");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"賣貨"+product);
--product;
notifyAll();
}