wait、notify、notifyAll和Condition
轉自
http://www.cnblogs.com/dolphin0520/p/3920385.html
用來學習記錄
可以用最經典的生產者-消費者模型:當佇列滿時,生產者需要等待佇列有空間才能繼續往裡面放入商品,而在等待的期間內,生產者必須釋放對臨界資源(即佇列)的佔用權。因為生產者如果不釋放對臨界資源的佔用權,那麼消費者就無法消費佇列中的商品,就不會讓佇列有空間,那麼生產者就會一直無限等待下去。因此,一般情況下,當佇列滿時,會讓生產者交出對臨界資源的佔用權,並進入掛起狀態。然後等待消費者消費了商品,然後消費者通知生產者佇列有空間了。同樣地,當佇列空時,消費者也必須等待,等待生產者通知它佇列中有商品了。這種互相通訊的過程就是執行緒間的協作。
一.wait()、notify()和notifyAll()
wait()、notify()和notifyAll()是Object類中的方法:
public final native void notify();
public final native void notifyAll();
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。
舉個簡單的例子:假如有三個執行緒Thread1、Thread2和Thread3都在等待物件objectA的monitor,此時Thread4擁有物件objectA的monitor,當在Thread4中呼叫objectA.notify()方法之後,Thread1、Thread2和Thread3只有一個能被喚醒。注意,被喚醒不等於立刻就獲取了objectA的monitor。假若在Thread4中呼叫objectA.notifyAll()方法,則Thread1、Thread2和Thread3三個執行緒都會被喚醒,至於哪個執行緒接下來能夠獲取到objectA的monitor就具體依賴於作業系統的排程了。
上面尤其要注意一點,一個執行緒被喚醒不代表立即獲取了物件的monitor,只有等呼叫完notify()或者notifyAll()並退出synchronized塊,釋放物件鎖後,其餘執行緒才可獲得鎖執行。
public class Test {
public static Object object = new Object();
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
}
System.out.println("執行緒"+Thread.currentThread().getName()+"獲取到了鎖");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println("執行緒"+Thread.currentThread().getName()+"呼叫了object.notify()");
}
System.out.println("執行緒"+Thread.currentThread().getName()+"釋放了鎖");
}
}
}
無論執行多少次,執行結果必定是:
執行緒Thread-1呼叫了object.notify() 執行緒Thread-1釋放了鎖 執行緒Thread-0獲取到了鎖
二.Condition
Condition(條件)是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現執行緒間的協作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現執行緒間協作更加安全和高效。因此通常來說比較推薦使用Condition,在阻塞佇列那一篇博文中就講述到了,阻塞佇列實際上是使用了Condition來模擬執行緒間協作。
- Condition是個介面,基本的方法就是await()和signal()方法;
- Condition依賴於Lock介面,生成一個Condition的基本程式碼是lock.newCondition()
- 呼叫Condition的await()和signal()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間才可以使用
Conditon中的await()對應Object的wait();
Condition中的signal()對應Object的notify();
Condition中的signalAll()對應Object的notifyAll()。
三.生產者-消費者模型的實現
1.使用Object的wait()和notify()實現:
public class Test {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
synchronized (queue) {
while(queue.size() == 0){
try {
System.out.println("佇列空,等待資料");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.poll(); //每次移走隊首元素
queue.notify();
System.out.println("從佇列取走一個元素,佇列剩餘"+queue.size()+"個元素");
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
synchronized (queue) {
while(queue.size() == queueSize){
try {
System.out.println("佇列滿,等待有空餘空間");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
queue.offer(1); //每次插入一個元素
queue.notify();
System.out.println("向佇列取中插入一個元素,佇列剩餘空間:"+(queueSize-queue.size()));
}
}
}
}
}
2.使用Condition實現
public class Test {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
lock.lock();
try {
while(queue.size() == 0){
try {
System.out.println("佇列空,等待資料");
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll(); //每次移走隊首元素
notFull.signal();
System.out.println("從佇列取走一個元素,佇列剩餘"+queue.size()+"個元素");
} finally{
lock.unlock();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
lock.lock();
try {
while(queue.size() == queueSize){
try {
System.out.println("佇列滿,等待有空餘空間");
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1); //每次插入一個元素
notEmpty.signal();
System.out.println("向佇列取中插入一個元素,佇列剩餘空間:"+(queueSize-queue.size()));
} finally{
lock.unlock();
}
}
}
}
}
參考資料:
《Java程式設計思想》