線程間的通信:多個線程在處理同一資源,但是任務卻不同。
一、等待喚醒機制
涉及的方法:
1.wait();讓線程處於凍結狀態,被wait的線程會被存儲到線程池中
2.notify();喚醒線程池中的一個任意線程
3.notifyAll();喚醒線程池中的所有線程
這些方法都必須定義在同步中,因為這些方法是用於操作線程狀態的方法,必須要明確到底操作的是哪個鎖上的線程
wait()對A鎖上面的線程進行操作後只能用A鎖的notify來喚醒。被wait之後的線程可認為放在線程池中。
為什麼操作線程的方法wait notify notifyAll定義在Object類中?
因為這些方法是監視器的方法,監視器其實就是鎖。
鎖可以是人一多對象,任意的對象調用的方式一定是定義在Object中。
package com.test2; class Resource { private String name; private int count=1; private boolean flag=false; public synchronized void set(String name) { if(flag) try{ this.wait();}catch(InterruptedException e){} this.name=name+count; count++; System.out.println(Thread.currentThread().getName() + "...." + "生產者"+".........." + this.name); flag=true; notify(); } public synchronized void out() { if(!flag) try{ this.wait();}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...."+"消費者"+"...."+this.name); flag=false; notify(); } } class Producer implements Runnable { private Resource r; Producer(Resource r) { this.r =r; } public void run() { while(true) { r.set("烤鴨"); } } } class Consumer implements Runnable { private Resource r; Consumer(Resource r) { this.r =r; } public void run() { while(true) { r.out(); } } } public class Demo { public static void main(String[] args) { Resource r=new Resource(); Producer a=new Producer(r); Consumer b=new Consumer(r); Thread t0=new Thread(a); Thread t1=new Thread(b); t0.start(); t1.start(); } }
產生的結果是:每生產一只就消費一只。
二、多生產者多消費者問題
將代碼改成兩個生產者兩個消費者:
Thread t0=new Thread(a);
Thread t1=new Thread(a);
Thread t2=new Thread(b);
Thread t3=new Thread(b);
t0.start();
t1.start();
t2.start();
t3.start();
可見還是產生了安全問題,關鍵在於這段代碼中:
if(flag)
try{ this.wait();}catch(InterruptedException e){} //t0 t1
當t0 t1被wait()掛在那後當再次喚醒的時候不會再次去判斷flag標記,而直接往下走再次去生產,導致發生錯誤。
只要將if改為while語句讓它返回再次判斷一次即可。
while(flag)
try{ this.wait();}catch(InterruptedException e){}
可是將代碼改成這樣後,卻出現了死鎖問題:
結果運行到這就卡死不動了。
原因:當走到t0(活),t1,t2,t3的情況時,t0走完線程代碼,喚醒不是t2或者t3中間的一個而是t1,標志位也改為那麼true,那麼t1也被直接wait了。
現在t0,t1,t2,t3全死,不會再喚醒,出現死鎖。而上面沒有出現的死鎖的原因在於用if語句,喚醒之後程序接著往下走,總會notify任何一個線程而不會
把所有線程都被wait,而用了while當喚醒之後首先判斷標志位,會直接掛死(喚醒的是同一方的線程)。
所以究其原因是沒有喚醒對方的線程。那麼怎麼保證每次都能至少喚醒對方的一個線程呢?
很遺憾,可是沒有辦法喚醒指定的線程,可以考慮將notify改為notifyAll每次喚醒所有wait的線程可以解決問題,搞定!再次會每生產一個就會消費一個。
總結:
if判斷標記,只有一次,會導致不該運行的線程運行了,出現數據錯誤的情況。
while判斷標記,解決線程獲取執行權後是否要運行。
notifyAll解決了本方線程一定會喚醒對方線程,notify可能只是喚醒了本方線程,沒有意義。且while標記+notify會導致死鎖。
所以只要是多生產者多消費者的情況,就用while+notifyAll。
問題:在用notifyAll喚醒線程時可能喚醒了本方線程,可是喚醒本方線程是沒有意義的(效率較低),本方線程已經干完活了,需要喚醒對方線程干活就行了。
在jdk1.5 java.util.concurrent.locks中提供了接口Lock
Lock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。
在同步代碼塊中,對於鎖的操作是隱式的,獲得和釋放都是隱式。jdk1.5後將鎖和同步封裝成對象,按面向對象的方式顯示操作鎖。
Object obj=new Object();
void show ()
{ synchronized(obj)
{
}
}
變為:
Lock lock=new ReetrantLock(); //互斥鎖,被一個線程獲取後不能被其他線程獲取。
void show ()
{ lock.lock();//獲得鎖
code。。。
lock.unlock();//釋放鎖
}
接口Conditionn將Object監視器方法(wait、notify、notifyAll)分解成截然不同的對象,以便通過這些對象與任意的lock組合使用。
其中Lock替代了synchronized方法與語句的使用,conditiont替代了Object監視器方法的使用。
Condition實例實質上被綁定在一個鎖上,要為Lock實例獲得Condition實例使用newCondition()方法。
對於上面問題的解決方法:生產者和消費者分別獲取一個condition對象,各自擁有一組監視器方法。生產者指定喚醒消費者,消費者指定喚醒生產者。
class Resource1 { private String name; private int count = 1; private boolean flag = false; //創建一個鎖對象。 Lock lock = new ReentrantLock(); //通過已有的鎖獲取該鎖上的監視器對象 // Condition con = lock.newCondition(); //通過已有的鎖獲取兩組監視器,一組監視生產者,一組監視消費者 Condition producer_con=lock.newCondition(); Condition consumer_con=lock.newCondition(); public void set(String name) { lock.lock(); try { while (flag) //if() try { producer_con.await(); } catch (InterruptedException e) {} this.name = name + count; count++; System.out.println(Thread.currentThread().getName() + "...." + "生產者" + "...." + this.name); flag = true; consumer_con.signal();//notify() } finally { lock.unlock(); } } public void out() { lock.lock(); try { while (!flag) //if() try { consumer_con.await(); } catch (InterruptedException e) {} System.out.println(Thread.currentThread().getName() + "...." + "消費者" + "...." + this.name); flag = false; producer_con.signal();//notify() } finally { lock.unlock(); } } } class Producer1 implements Runnable { private Resource r; Producer1(Resource r) { this.r =r; } public void run() { while(true) { r.set("烤鴨"); } } } class Consumer1 implements Runnable { private Resource r; Consumer1(Resource r) { this.r =r; } public void run() { while(true) { r.out(); } } } public class LockDemo { public static void main(String[] args) { Resource r=new Resource(); Producer a=new Producer(r); Consumer b=new Consumer(r); Thread t0=new Thread(a); Thread t1=new Thread(a); Thread t2=new Thread(b); Thread t3=new Thread(b); t0.start(); t1.start(); t2.start(); t3.start(); } } View Code
改動的代碼如下:
總結:
* Lock接口:出現替代了同步代碼塊或者同步函數。將同步隱式操作變成顯示鎖操作。同時更加靈活。可以一個鎖上加上多組監視器。
* lock():獲取鎖
* unlock():釋放鎖,通常要要定義在finally代碼塊當中。
* Condition接口:出現替代了Object中wait notify notifyAll方法,這些監視器方法單獨進行了封裝,變成Condition監視器對象。
* 可以任意的鎖進行組合。
* await()——wait()
* signal()——notify()
* signalAll()——notifyAll()
示例:假定有一個綁定的緩沖區,它支持 put
和 take
方法。如果試圖在空的緩沖區上執行 take
操作,則在某一個項變得可用之前,線程將一直阻塞;
如果試圖在滿的緩沖區上執行 put
操作,則在有空間變得可用之前,線程將一直阻塞。
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }