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 |
|
從這三個方法的文字描述可以知道以下幾點資訊:
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 方法也就沒有必要執行了