1. 程式人生 > >wait/notify 方法執行緒間的通訊,只隨機通知一個執行緒進行喚醒,一次性喚醒所有執行緒, wait(long) 的使用,通知過早會打亂順序正常的邏輯順序

wait/notify 方法執行緒間的通訊,只隨機通知一個執行緒進行喚醒,一次性喚醒所有執行緒, wait(long) 的使用,通知過早會打亂順序正常的邏輯順序

一.wait()、notify()和notifyAll()

  wait()、notify()和notifyAll()是Object類中的方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

/**

* Wakes up a single thread that is waiting on this object's

* monitor. If any threads are waiting on this object, one of them

* is chosen to be awakened. The choice is arbitrary and occurs at

* the discretion of the implementation. A thread waits on an object's

* monitor by calling one of the wait methods

*/

public final native void notify();

/**

* Wakes up all threads that are waiting on this object's monitor. A

* thread waits on an object's monitor by calling one of the

* wait methods.

*/

public final native void notifyAll();

/**

* Causes the current thread to wait until either another thread invokes the

* {@link java.lang.Object#notify()} method or the

* {@link java.lang.Object#notifyAll()} method for this object, or a

* specified amount of time has elapsed.

* <p>

* The current thread must own this object's monitor.

*/

public final native void wait(long timeout) throws InterruptedException;

   從這三個方法的文字描述可以知道以下幾點資訊:

  1)wait()、notify()和notifyAll()方法是本地方法,並且為final方法,無法被重寫。

  2)呼叫某個物件的wait()方法能讓當前執行緒阻塞,並且當前執行緒必須擁有此物件的monitor(即鎖)

  3)呼叫某個物件的notify()方法能夠喚醒一個正在等待這個物件的monitor的執行緒,如果有多個執行緒都在等待這個物件的monitor,則只能喚醒其中一個執行緒;

  4)呼叫notifyAll()方法能夠喚醒所有正在等待這個物件的monitor的執行緒;

  有朋友可能會有疑問:為何這三個不是Thread類宣告中的方法,而是Object類中宣告的方法(當然由於Thread類繼承了Object類,所以Thread也可以呼叫者三個方法)?其實這個問題很簡單,由於每個物件都擁有monitor(即鎖),所以讓當前執行緒等待某個物件的鎖,當然應該通過這個物件來操作了。而不是用當前執行緒來操作,因為當前執行緒可能會等待多個執行緒的鎖,如果通過執行緒來操作,就非常複雜了。

  上面已經提到,如果呼叫某個物件的wait()方法,當前執行緒必須擁有這個物件的monitor(即鎖),因此呼叫wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

  呼叫某個物件的wait()方法,相當於讓當前執行緒交出此物件的monitor,然後進入等待狀態,等待後續再次獲得此物件的鎖(Thread類中的sleep方法使當前執行緒暫停執行一段時間,從而讓其他執行緒有機會繼續執行,但它並不釋放物件鎖);

  notify()方法能夠喚醒一個正在等待該物件的monitor的執行緒,當有多個執行緒都在等待該物件的monitor的話,則只能喚醒其中一個執行緒,具體喚醒哪個執行緒則不得而知。

  同樣地,呼叫某個物件的notify()方法,當前執行緒也必須擁有這個物件的monitor,因此呼叫notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

  nofityAll()方法能夠喚醒所有正在等待該物件的monitor的執行緒,這一點與notify()方法是不同的。

  這裡要注意一點:notify()和notifyAll()方法只是喚醒等待該物件的monitor的執行緒,並不決定哪個執行緒能夠獲取到monitor。

package com.bjsxt.base.conn008;

import java.util.ArrayList;
import java.util.List;

public class ListAdd1 {

	private volatile static List list = new ArrayList();	
	
	public void add(){
		list.add("bjsxt");
	}
	public int size(){
		return list.size();
	}
	
	public static void main(String[] args) {
		
		final ListAdd1 list1 = new ListAdd1();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					for(int i = 0; i <10; i++){
						list1.add();
						System.out.println("當前執行緒:" + Thread.currentThread().getName() + "添加了一個元素..");
						Thread.sleep(500);
					}	
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					if(list1.size() == 5){
						System.out.println("當前執行緒收到通知:" + Thread.currentThread().getName() + " list size = 5 執行緒停止..");
						throw new RuntimeException();
					}
				}
			}
		}, "t2");		
		//t1 t2 誰在前無所謂,因為是非同步的
		t1.start();
		t2.start();
	}
	
	
}

結果為 只要是 t2執行緒 list size = 5 執行緒就停止了丟擲異常

當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒收到通知:t2 list size = 5 執行緒停止..
Exception in thread "t2" java.lang.RuntimeException
	at com.bjsxt.base.conn008.ListAdd1$2.run(ListAdd1.java:42)
	at java.lang.Thread.run(Thread.java:722)
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..

方法wait() 的作用是使當前執行程式碼的執行緒進行等待,wait() 方法是object 類的方法,該方法用來將當前執行緒置入“預執行佇列中”,並且在wait() 所在的程式碼行處停止執行,直到接到通知或被中斷為止,在呼叫wait() 之前,執行緒必須獲得該物件的物件級別鎖,即只能是在同步方法或同步塊中呼叫wait() 方法,在執行wait() 方法後,當前執行緒釋放鎖,在wait() 返回前,執行緒與其他執行緒競爭重新獲得所,如果呼叫wait() 時沒有持有適當的鎖,則跑出異常,IIIegalMonitorStateException 它是RuntimeException 的一個子類,因此,不需要try-catch 語句進行捕捉異常

方法notify() 也要在同步方法或者同步塊中呼叫,即在呼叫前,執行緒也必須獲得該物件的級別鎖,如果呼叫notify() 時沒有持有適當的鎖,也會丟擲IIIegalMonitorStateException 該方法用來通知那些可能等待該物件的物件鎖的其他執行緒,如果有多個執行緒等待,則由執行緒規劃器隨機挑選一個呈wait 狀態的執行緒,對其發出通知notify ,並使它等待獲取該物件的物件鎖,呈wait狀態的執行緒也並不能馬上獲取該物件鎖,要等到執行notify() 方法的執行緒將程式執行完,也就是退出synchronized 程式碼塊以後,當前執行緒才會釋放鎖,而呈wait 狀態所在的執行緒才可以獲取該物件鎖,當第一個獲得了該物件的wait 執行緒執行完畢以後,它會釋放掉該物件的鎖,此時如果沒有再次使用notify 語句,則即便對該物件已經空閒,其他wait 狀態等待的執行緒由於沒有得到該物件的通知,還會繼續阻塞在wait 狀態,直到這個物件發出一個notify或者notifyAll .

package test;

public class Test1 {
	public static void main(String[] args) {
		try {
			String newString = new String("");
			newString.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:485)
	at test.Test1.main(Test1.java:7)

出現異常的原因是沒有“物件監視器”,也就是沒有加同步鎖

package test;
public class Test2 {
	public static void main(String[] args) {
		try {
			String lock = new String();
			System.out.println("syn上面");
			synchronized (lock) {
				System.out.println("syn第一行");
				lock.wait();
				System.out.println("wait下的程式碼!");
			}
			System.out.println("syn下面的程式碼");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

​

結果為

syn上面
syn第一行

把上述的程式碼修改為wait/notify 的方式

package com.bjsxt.base.conn008;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * @author alienware
 *
 */
public class ListAdd2 {
	private volatile static List list = new ArrayList();	
	
	public void add(){
		list.add("bjsxt");
	}
	public int size(){
		return list.size();
	}
	
	public static void main(String[] args) {
		
		final ListAdd2 list2 = new ListAdd2();
		final Object lock = new Object();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
                                        //t1執行緒拿著鎖,就不釋放了,直到執行緒執行完畢以後才會執行t2 
					synchronized (lock) {
						System.out.println("t1啟動..");
						for(int i = 0; i <10; i++){
							list2.add();
							System.out.println("當前執行緒:" + Thread.currentThread().getName() + "添加了一個元素..");
							Thread.sleep(500);
							if(list2.size() == 5){
								System.out.println("已經發出通知..");
								lock.notify();
							}
						}						
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("t2啟動..");
					if(list2.size() != 5){
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("當前執行緒:" + Thread.currentThread().getName() + "收到通知執行緒停止..");
					throw new RuntimeException();
				}
			}
		}, "t2");
                //t2 執行緒一定是在前面的,程式執行到lock.wait(); 由於開始進來的時候,list 的一定是不等於5的,所以就一直等待,由於wait 等待的時候是釋放鎖的,所以鎖釋放了開始執行t1執行緒
		t2.start();
		t1.start();
		
	}
	
}

結果是

t2啟動..
t1啟動..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
已經發出通知..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t1添加了一個元素..
當前執行緒:t2收到通知執行緒停止..
Exception in thread "t2" java.lang.RuntimeException
	at com.bjsxt.base.conn008.ListAdd2$2.run(ListAdd2.java:60)
	at java.lang.Thread.run(Thread.java:722)

但是這樣寫有一個弊端,不能做到實時通知的效果應該改為下面這個樣子

package com.bjsxt.base.conn008;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * @author alienware
 *
 */
public class ListAdd2 {
	private volatile static List list = new ArrayList();
	//是執行緒包當中的類加上了這個就可以做到實時通知的目的,但是這個類不能與synchronized 合在一起用
	final static CountDownLatch countDownLatch =new CountDownLatch(2);
	
	public void add(){
		list.add("bjsxt");
	}
	public int size(){
		return list.size();
	}
	
	public static void main(String[] args) {
		
		final ListAdd2 list2 = new ListAdd2();
		final Object lock = new Object();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					//CountDownLatch不能與synchronized 合在一起用
//					synchronized (lock) {
						System.out.println("t1啟動..");
						for(int i = 0; i <10; i++){
							list2.add();
							System.out.println("當前執行緒:" + Thread.currentThread().getName() + "添加了一個元素..");
							Thread.sleep(500);
							if(list2.size() == 5){
								System.out.println("已經發出通知..");
//								lock.notify();
								countDownLatch.countDown();
								countDownLatch.countDown();
							}
//						}						
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				////CountDownLatch不能與synchronized 合在一起用
//				synchronized (lock) {
					System.out.println("t2啟動..");
					if(list2.size() != 5){
						try {
//							lock.wait();
							countDownLatch.await();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("當前執行緒:" + Thread.currentThread().getName() + "收到通知執行緒停止..");
					throw new RuntimeException();
//				}
			}
		}, "t2");	
		t2.start();
		t1.start();
		
	}
	
}

只隨機通知一個執行緒進行喚醒

原始碼 notifyOne

package extthread;
import service.Service;
public class NotifyThread extends Thread {
	private Object lock;
	public NotifyThread(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	public void run() {
		synchronized (lock) {
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
			lock.notify();
		}
	}
}

一共是ThreadA ThreadB ThreadC 三個類的程式碼是完全一樣的

package extthread;
import service.Service;
public class ThreadA extends Thread {
	private Object lock;
	public ThreadA(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	public void run() {
		Service service = new Service();
		service.testMethod(lock);
	}
}
package service;
public class Service {
	public void testMethod(Object lock) {
		try {
			synchronized (lock) {
				System.out.println("begin wait() ThreadName="
						+ Thread.currentThread().getName());
				lock.wait();
                     //每一個執行緒進來以後,鎖都釋放了,ThreadA ThreadB ThreadC  三種執行緒
				System.out.println("  end wait() ThreadName="
						+ Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
package test;
import extthread.NotifyThread;
import extthread.ThreadA;
import extthread.ThreadB;
import extthread.ThreadC;
public class Test {
	public static void main(String[] args) throws InterruptedException {
		Object lock = new Object();
		ThreadA a = new ThreadA(lock);
		a.start();
		ThreadB b = new ThreadB(lock);
		b.start();
		ThreadC c = new ThreadC(lock);
		c.start();
		Thread.sleep(1000);
		NotifyThread notifyThread = new NotifyThread(lock);
		notifyThread.start();
	}
}

隨機會喚醒一個執行緒

begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-0
  end wait() ThreadName=Thread-1
  end wait() ThreadName=Thread-0
  end wait() ThreadName=Thread-2

多次呼叫notify() 方法喚醒了全部waiting 中的執行緒

一次性喚醒所有執行緒

package extthread;

public class NotifyThread extends Thread {
	private Object lock;
	public NotifyThread(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	public void run() {
		synchronized (lock) {
			lock.notifyAll();
		}
	}
}

一次性喚醒所有執行緒

begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-1
  end wait() ThreadName=Thread-1
  end wait() ThreadName=Thread-2
  end wait() ThreadName=Thread-0

 wait(long) 的使用

是等待某一個時間是否有執行緒對鎖進行喚醒,如果超過這個時間則自動喚醒

原始碼 waitHasParamMethod

package myrunnable;
public class MyRunnable {
	static private Object lock = new Object();
	static private Runnable runnable1 = new Runnable() {
		@Override
		public void run() {
			try {
				synchronized (lock) {
					System.out.println("wait begin timer="
							+ System.currentTimeMillis());
					lock.wait(5000);
					System.out.println("wait   end timer="
							+ System.currentTimeMillis());
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	};
	static private Runnable runnable2 = new Runnable() {
		@Override
		public void run() {
			synchronized (lock) {
				System.out.println("notify begin timer="
						+ System.currentTimeMillis());
				lock.notify();
				System.out.println("notify   end timer="
						+ System.currentTimeMillis());
			}
		}
	};
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(runnable1);
		t1.start();
		Thread.sleep(3000);
		Thread t2 = new Thread(runnable2);
		t2.start();
	}
}
wait begin timer=1533888100845
notify begin timer=1533888103846
notify   end timer=1533888103847
wait   end timer=1533888103847

通知過早會打亂順序正常的邏輯順序

原始碼 firstNotify

package test;
public class MyRun {
	private String lock = new String("");
	private boolean isFirstRunB = false;
	private Runnable runnableA = new Runnable() {
		@Override
		public void run() {
			try {
				synchronized (lock) {
					while (isFirstRunB == false) {
						System.out.println("begin wait");
						lock.wait();
						System.out.println("end wait");
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	};
	private Runnable runnableB = new Runnable() {
		@Override
		public void run() {
			synchronized (lock) {
				System.out.println("begin notify");
				lock.notify();
				System.out.println("end notify");
				isFirstRunB = true;
			}
		}
	};
	public static void main(String[] args) throws InterruptedException {
		MyRun run = new MyRun();
		Thread a = new Thread(run.runnableA);
		a.start();
		Thread.sleep(100);//把這個註釋掉永遠不會被通知
		Thread b = new Thread(run.runnableB);
		b.start();
	}
}

如果先通知了,則wait 方法也就沒有必要執行了