1. 程式人生 > >java多執行緒基礎總結【四】虛假喚醒

java多執行緒基礎總結【四】虛假喚醒

我們先來看一組例子
package com.buerc.thread;

public class TesProducerAndConsumer {
	public static void main(String[] args) {
		Clerk clerk=new Clerk();
		
		Producer producer=new Producer(clerk);
		Consumer consumer=new Consumer(clerk);
		
		new Thread(producer,"生產者A1").start();
		new Thread(consumer,"消費者B1").start();
	}
}

class Clerk{//店員類
	private static final int TOTAL=1;//數字取1是為了放大問題
	private int num=0;
	
	public synchronized void get() {//店員買貨
		if(num>=TOTAL) {
			System.out.println("庫存已滿,無法進貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName()+" : "+ (num++));
			this.notifyAll();
		}
	}
	
	public synchronized void sale() {//店員賣貨
		if(num<=0) {
			System.out.println("庫存已空,無法賣貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName()+" : "+(num--));
			this.notifyAll();
		}
	}
}

class Producer implements Runnable{
	private Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			try {
				Thread.sleep(200);//放大問題
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

class Consumer implements Runnable{
	private Clerk clerk;
	
	public Consumer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			clerk.sale();
		}
	}
}
這樣看起來似乎很合理。一個生產者生產,一個消費者消費,但是執行起來卻是控制檯沒停。而我們這裡是for20次迴圈,按理來說程式最終會終止,可情況恰恰相反。問題產生的根源是,由於生產者現象睡眠了200毫秒,因而可能產生的情況是最後消費者執行緒迴圈走完了然後就真的結束了,而生產者執行緒由於wait沒有執行緒來喚醒,所以最終導致一直等待,因而程式不會結束,控制檯就不終止。解決方法:
package com.buerc.thread;

public class TesProducerAndConsumer {
	public static void main(String[] args) {
		Clerk clerk=new Clerk();
		
		Producer producer=new Producer(clerk);
		Consumer consumer=new Consumer(clerk);
		
		new Thread(producer,"生產者A1").start();
		new Thread(consumer,"消費者B1").start();
	}
}

class Clerk{//店員類
	private static final int TOTAL=1;//數字取1是為了放大問題
	private int num=0;
	
	public synchronized void get() {//店員買貨
		if(num>=TOTAL) {
			System.out.println("庫存已滿,無法進貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+ (num++));
		this.notifyAll();
	}
	
	public synchronized void sale() {//店員賣貨
		if(num<=0) {
			System.out.println("庫存已空,無法賣貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+(num--));
		this.notifyAll();
		
	}
}

class Producer implements Runnable{
	private Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			try {
				Thread.sleep(200);//放大問題
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

class Consumer implements Runnable{
	private Clerk clerk;
	
	public Consumer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			clerk.sale();
		}
	}
}

這裡把同步方法的else去掉了,那麼無論最終哪個執行緒先走完,都會執行wait後面的方法,即它在結束前會喚醒等待的執行緒,因而這個執行緒最終也會完整的執行完,最後程式終止。這種方法的確解決了程式無法終止的問題,但是我們來看看,當我們多加一對消費者執行緒和生產者執行緒時。

package com.buerc.thread;

public class TesProducerAndConsumer {
	public static void main(String[] args) {
		Clerk clerk=new Clerk();
		
		Producer producer=new Producer(clerk);
		Consumer consumer=new Consumer(clerk);
		Producer producer2=new Producer(clerk);
		Consumer consumer2=new Consumer(clerk);
		
		new Thread(producer,"生產者A1").start();
		new Thread(consumer,"消費者B1").start();
		new Thread(producer2,"生產者A2").start();
		new Thread(consumer2,"消費者B2").start();
	}
}

class Clerk{//店員類
	private static final int TOTAL=1;//數字取1是為了放大問題
	private int num=0;
	
	public synchronized void get() {//店員買貨
		if(num>=TOTAL) {
			System.out.println("庫存已滿,無法進貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+ (num++));
		this.notifyAll();
	}
	
	public synchronized void sale() {//店員賣貨
		if(num<=0) {
			System.out.println("庫存已空,無法賣貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+(num--));
		this.notifyAll();
		
	}
}

class Producer implements Runnable{
	private Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			try {
				Thread.sleep(200);//放大問題
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

class Consumer implements Runnable{
	private Clerk clerk;
	
	public Consumer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			clerk.sale();
		}
	}
}
這種情況出現了產品數為負的情況,肯定不合適。這就是虛假喚醒,因為有可能num==0,然後兩個消費者執行緒都wait,此時生產者執行num++後,在喚醒卻是喚醒了所有等待的執行緒,此時這兩個消費者執行緒搶佔資源後立馬執行wait之後的操作,即num--就會出現產品為負的情況。為了避免這種情況我們應該讓wait()在while迴圈中。(API上關於虛假喚醒的解決方式)
package com.buerc.thread;

public class TesProducerAndConsumer {
	public static void main(String[] args) {
		Clerk clerk=new Clerk();
		
		Producer producer=new Producer(clerk);
		Consumer consumer=new Consumer(clerk);
		Producer producer2=new Producer(clerk);
		Consumer consumer2=new Consumer(clerk);
		
		new Thread(producer,"生產者A1").start();
		new Thread(consumer,"消費者B1").start();
		new Thread(producer2,"生產者A2").start();
		new Thread(consumer2,"消費者B2").start();
	}
}

class Clerk{//店員類
	private static final int TOTAL=1;//數字取1是為了放大問題
	private int num=0;
	
	public synchronized void get() {//店員買貨
		while(num>=TOTAL) {
			System.out.println("庫存已滿,無法進貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+ (num++));
		this.notifyAll();
	}
	
	public synchronized void sale() {//店員賣貨
		while(num<=0) {
			System.out.println("庫存已空,無法賣貨");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" : "+(num--));
		this.notifyAll();
		
	}
}

class Producer implements Runnable{
	private Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			try {
				Thread.sleep(200);//放大問題
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

class Consumer implements Runnable{
	private Clerk clerk;
	
	public Consumer(Clerk clerk) {
		this.clerk=clerk;
	}
	
	@Override
	public void run() {
		for (int i = 0; i<20; i++) {
			clerk.sale();
		}
	}
}