為什么80%的碼農都做不了架構師?>>> ??
Lock(鎖)&Condition(條件)實現線程同步通信
接下來介紹,java5線程并發庫里面的鎖。跟鎖有關的類和接口主要是位于java.util.concurrent.locks包。
Lock(鎖)接口實現線程間互斥
下面我們先來了解Lock(鎖)這個接口,這個鎖的作用呢就非常類實于我們傳統模型線程中的synchronized關鍵字,它的作用主要用于線程互斥。就好比有一個人進了一個廁所,把那個廁所里面的門給鎖上,那別人就進不去了,他出來的時候把那把鎖拿掉,別人就可以進去了。每個人進去的時候都可以上鎖,上鎖了,別人就進不去了。它相比這個synchronized來說呢,它更加面向對象。鎖本身就是一個對象,鎖上進不去,打開,別人可以進去。這樣就更面向對象。下面我們通過代碼來跟synchronized關鍵字對比一下看這個鎖怎么用。
ReentrantLock類實現可重入的互斥鎖
Lock是一個接口,當我們想要獲得一個它的對象的時候就要new一個它的實現了類,這里我們new ReentrantLock();這是一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。這樣我們就制造了一把鎖,當A線程來調用的時候,我們就在要線程互斥的代碼的前面調用lock.lock();上一把鎖。就是A線程一進去以后,馬上就把鎖鎖上。當B線程再來的時候呢,能不能進呢,那就看B線程是不是也想要這把鎖,也必須用這把鎖,如果B線程用的是另外一把鎖,B線程是可以進的,只有B線程也要搶這一把鎖的時候,它搶不到了。當A線程執行完了要線程互斥的這部分代碼以后呢,我們調用lock.unlock();方法,把鎖交回去。這樣兩個線程就不會打架了。但是這個時候還有一個問題,如果在被鎖上的這部分代碼內部拋了異常,那么整個方法就跳出了,代碼不會向下執行,這個時候這個鎖還沒有釋放。就好比一個人進去了,好家伙,他暈倒在里面了,這時候誰也進不去了,所以,在這種情況下,我們最好是把這部分被鎖包裹的代碼try起來,在finally代碼塊里面釋放鎖。就是以防萬一,就是說無論如何,不管是正常還是非正常的退出,我們都應該把鎖給釋放。這樣就保證別人也可以進去,即使之前哪個線程在拿到鎖之后死掉了,別人還是可以進去。否則就麻煩了。
public class LockTest { ? ??? /** ???? * @param args ???? */ ??? public static void main(String[] args) { ??????? new LockTest().init(); ??? } ? ??? private void init() { ??????? final Outputer outputer = new Outputer(); ??????? new Thread(new Runnable() { ??????????? @Override ??????????? public void run() { ??????????????? while (true) { ??????????????????? try { ??????????????????????? Thread.sleep(10); ??????????????????? } catch (InterruptedException e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????????? outputer.output("zhangxiaoxiang"); ??????????????? } ? ??????????? } ??????? }).start(); ? ??????? new Thread(new Runnable() { ??????????? @Override ??????????? public void run() { ??????????????? while (true) { ??????????????????? try { ??????????????????????? Thread.sleep(10); ??????????????????? } catch (InterruptedException e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????????? outputer.output("lihuoming"); ??????????????? } ? ??????????? } ??????? }).start(); ? ??? } ? ??? static class Outputer { ??????? Lock lock = new ReentrantLock(); ? ??????? public void output(String name) { ??????????? int len = name.length(); ??????????? lock.lock(); ??????????? try { ??????????????? for (int i = 0; i < len; i++) { ??????????????????? System.out.print(name.charAt(i)); ??????????????? } ??????????????? System.out.println(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ??? } } |
static class Outputer { ? ??????? public void output(String name) { ??????????? int len = name.length(); ??????????? synchronized (Outputer.class) { ??????????????? for (int i = 0; i < len; i++) { ??????????????????? System.out.print(name.charAt(i)); ??????????????? } ??????????????? System.out.println(); ??????????? } ??????? } ? |
讀寫鎖ReadWriteLock接口和實現類ReentrantReadWriteLock可重入讀寫鎖
接下來我們來看,java5中的鎖比傳統線程模型中的synchronized關鍵字更強。它還可以實現讀寫鎖ReadWriteLock接口(用的是鎖分離的優化思想:只要兩個線程的操作互不影響,鎖就可以分離)。什么叫讀寫鎖呢?就是多個讀鎖不互斥,而讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。這在我們實際項目當中,我們是經常需要這個功能的。我們為什么要上鎖呀,就是為了保證數據的完整性。如果才讀了一半,你就跑進去寫,那數據改了,我都會來的就亂了。如果大家都在讀,如果想在這個數據是3,現在100個人在讀,請問有沒有問題?肯定沒有問題,怎么讀都是3。就是怕這個讀的過程中,別人跑去改了,如果全是讀,是沒有問題的。如果大家全是讀的時候,大家還上鎖,好不好?不好,性能低,沒有并發了。我們希望可以提高性能,大家可以同時讀,但是又不打架。什么時候會大家呢,就在于寫數據的時候容易打架,在修改數據的時候才打架。在讀的時候,又跑進去寫,容易出問題。在寫的時候還沒寫完,又跑進去寫,容易出問題。下面我們就寫一個讀寫鎖的應用。我們產生3個線程負責讀數據,還產生3個線程負責寫數據,如果我們不上讀寫鎖,我們會看到讀和寫交替的運行。就讀中有寫,寫中有讀。如果我們上了讀寫鎖,我們將看到什么效果呢?讀中有讀,但是讀中沒有寫,寫中沒有讀,寫中也沒有寫。讀寫鎖非常重要,能夠提高性能,又能夠互斥。這個在java5以前干不了這個事,現在java5幫我們提供了這種功能。非常好。
public class ReadWriteLockTest { ??? public static void main(String[] args) { ??????? final Queue3 q3 = new Queue3(); ??????? for (int i = 0; i < 3; i++) { ??????????? new Thread() { ??????????????? public void run() { ??????????????????? while (true) { ??????????????????????? q3.get(); ??????????????????? } ??????????????? } ? ??????????? }.start(); ? ??????????? new Thread() { ??????????????? public void run() { ??????????????????? while (true) { ??????????????????????? q3.put(new Random().nextInt(10000)); ??????????????????? } ??????????????? } ? ??????????? }.start(); ??????? } ? ??? } } ? class Queue3 { ??? private Object data = null;// 共享數據,只能有一個線程能寫該數據,但可以有多個線程同時讀該數據。 ? ??? ReadWriteLock rwl = new ReentrantReadWriteLock(); ? ??? public void get() { ??????? rwl.readLock().lock(); ??????? try { ??????????? System.out.println(Thread.currentThread().getName() + " be ready to read data!"); ??????????? Thread.sleep((long) (Math.random() * 1000)); ??????????? System.out.println(Thread.currentThread().getName() + "have read data :" + data); ??????? } catch (InterruptedException e) { ??????????? e.printStackTrace(); ??????? } finally { ??????????? rwl.readLock().unlock(); ??????? } ??? } ? ??? public void put(Object data) { ? ??????? rwl.writeLock().lock(); ??????? try { ??????????? System.out.println(Thread.currentThread().getName() + " be ready to write data!"); ??????????? Thread.sleep((long) (Math.random() * 1000)); ??????????? this.data = data; ??????????? System.out.println(Thread.currentThread().getName() + " have write data: " + data); ??????? } catch (InterruptedException e) { ??????????? e.printStackTrace(); ??????? } finally { ??????????? rwl.writeLock().unlock(); ??????? } ? ??? } } |
下面我們來看一個讀寫鎖的比較好的例子。
先簡單說一下Hibernate中session的load和get方法的區別:
get方法是直接去數據庫里面查,如果有就返回,沒有就返回null.
load方法得到的是一個代理對象,這個代理對象里面封裝了我們想要的真實的對象的引用和它的id作為成員字段。他不會先去查數據庫,首先會把方法參數中傳入的id賦值給自己的id字段。然后返回這個代理對象。當我們要調用方法獲取對象中id之外的字段的時候,它會先判斷成員字段,就是那個真實對象的引用是否為空,如果為空的話,他就調用get方法去查詢數據庫,如果查到就賦值給那個真實對象的引用變量,并返回,沒查到就拋出異常。
好了,明白上面的思想,我們再來看下面的例子。
class CachedData { ??? Object data; ? ??? volatile boolean cacheValid; ? ??? ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); ? ??? void processCachedData() {//處理緩存數據 ???? rwl.readLock().lock();[z1]? ???? if (!cacheValid) {//檢查有沒有緩存的數據 ??????? // 在獲得寫鎖之前必須釋放讀鎖 ??????? rwl.readLock().unlock();[z2]? ??????? rwl.writeLock().lock();[z3]? ??????? // 重新檢查狀態,因為另一個線程可能已經獲得寫入鎖定并在我們完成之前改變了狀態。 ??????? if (!cacheValid) {//如果沒有緩存數據 ????????? data = ...//去獲取數據 ????????? cacheValid = true;//獲取數據之后把緩存檢查值設為true ??????? } ??????? // 通過在釋放寫鎖之前獲取讀鎖來降級 ??????? rwl.readLock().lock();[z4]? ??????? rwl.writeLock().unlock(); // 解鎖寫入,仍然保持讀取 ???? } ? ???? use(data);//使用數據 ???? rwl.readLock().unlock(); ?? } } |
以上代碼,其實就是,大家都去讀數據,然后又怕某一個線程在寫數據,所以大家一開始都掛了讀鎖,但是發現沒有數據可讀呢,第一個家伙發現沒有數據可讀呢,他馬上掛一把寫鎖,就讓別人讀的暫時先不要進來了,等著,因為現在沒有數據,我把數據寫完了,我再把寫鎖解放,我也掛讀鎖,恢復成為讀鎖,大家就又都可以讀了。這個例子就是一個緩存,一個代理的緩存,一個實體的緩存,不是緩存系統。
下面我們再做一個思考題,請你寫出一個緩存系統的偽代碼。這道題目跟我們上面講的緩存代理對象不一樣,緩存系統就是說可以裝很多個對象,我們上面的緩存代理對象只能夠裝一個對象。什么是緩存呢?就是人家要取數據,別人調用我,調用我的一個方法叫getData(),我就應該檢查,我內部是否有這個數據,如果有這個數據,我就直接給人家,如果沒有這個數據,我就去數據庫里面查,我查到以后,我就把查到的數據存到我的內存里面來,下次再有別人再找我要這個數據,就直接返回給人家,就不需要再查數據庫了。這就是緩存系統的概念。你要拿對象不要直接找數據庫,找我。如果我內部沒有,我去查數據庫,給你。和你直接查數據庫是一樣的。好處就在于,你下次再來找我的時候,我就不再查數據庫了,我直接給你。這就是Cache.
因為我們是定義自己的Cache,我要存一堆對象,那如果人家來找我取某個對象的時候,怎么來取呢?我們定義一個key(key存的就是對象的名字),讓要取對象的人把key給我,我就把這個key對應的對象給他。所以在緩存對象的內部,我們要定義一個Map成員,用來有對應規則的存儲對象。那么現在人家來找我取數據,我是怎么做呢?我就先到map里面去get(key),來得到一個對象,如果這個對象為null,我就去查數據庫,查到以后我再返回這個對象,在返回之間,我現在自己的map里面存一份。這就是一個簡單的緩存系統。
public class CacheDemo { ? ??? private Map<String, Object> cache = new HashMap<String, Object>(); ? ??? public static void main(String[] args) { ??????? // TODO Auto-generated method stub ? ??? } ? ??? private ReadWriteLock rwl = new ReentrantReadWriteLock(); ? ??? public Object getData(String key) { ??????? rwl.readLock().lock(); ??????? Object value = null; ??????? try { ??????????? value = cache.get(key); ??????????? if (value == null) { ??????????????? rwl.readLock().unlock(); ??????????????? rwl.writeLock().lock(); ??????????????? try { ??????????????????? if (value == null) { ??????????????????????? value = "aaaa";// 實際是去queryDB(); ??????????????????? } ??????????????? } finally { ??????????????????? rwl.writeLock().unlock(); ??????????????? } ??????????????? rwl.readLock().lock(); ??????????? } ??????? } finally { ??????????? rwl.readLock().unlock(); ??????? } ??????? return value; ??? } } |
Condition實現互斥線程間的通信
接下來我們來介紹java5并發庫這個java.util.concurrent.locks子包里面的另一個東西,叫Condition,叫條件。這個Condition到底是什么呢?是執行條件,就類似與我們傳統線程同步模型中的Object的wait()方法和notify()方法。我們說,鎖,只能實現互斥,就是說,我進去了,你不能進來。但是無法實現通信。什么叫通信呢?說,哥們,你可以執行了。這就是通信。Condition就是用來解決這個問題的。就是說CPU即使分給了我,我進來了,但是,我可以讓出CPU,說,兄弟,你干吧。我暫停,兄弟你干完了,再通知我,說,大哥,謝謝你,你再干吧。這就是通信。下面我們來看一個Condition實現線程通信的代碼例子,對比Object的wait()和notify();注意:Condition是基于鎖Lock之上的。Condition一定是在某個lock對象基礎上,也就是說,是基于某個Lock對象以后,在lock對象下面new出一個Condition(調用lock.newCondition();方法)。
public class ConditionCommunication { ? ??? /** ???? * @param args ???? */ ??? public static void main(String[] args) { ? ??????? final Business business = new Business(); ??????? new Thread(new Runnable() { ? ??????????? @Override ??????????? public void run() { ? ??????????????? for (int i = 1; i <= 50; i++) { ??????????????????? business.sub(i); ??????????????? } ? ??????????? } ??????? }).start(); ? ??????? for (int i = 1; i <= 50; i++) { ??????????? business.main(i); ??????? } ? ??? } ? ??? static class Business { ??????? Lock lock = new ReentrantLock(); ? ??????? Condition condition = lock.newCondition(); ? ??????? private boolean bShouldSub = true; ? ??????? public void sub(int i) { ??????????? lock.lock(); ??????????? try { ??????????????? while (!bShouldSub) { ??????????????????? try { ??????????????????????? condition.await(); ??????????????????? } catch (Exception e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????????? for (int j = 1; j <= 10; j++) { ??????????????????? System.out.println("sub thread sequence of " + j + ",loop of " + i); ??????????????? } ??????????????? bShouldSub = false; ??????????????? condition.signal(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ? ??????? public void main(int i) { ??????????? lock.lock(); ??????????? try { ??????????????? while (bShouldSub) { ??????????????????? try { ??????????????????????? condition.await(); ??????????????????? } catch (Exception e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????????? for (int j = 1; j <= 100; j++) { ??????????????????? System.out.println("main thread sequence of " + j + ",loop of " + i); ??????????????? } ??????????????? bShouldSub = true; ??????????????? condition.signal(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ? ??? } } |
class Business { ??? private boolean bShouldSub = true; ??? public synchronized void sub(int i){ ??????? while(!bShouldSub){ ??????????? try { ??????????????? this.wait(); ??????????? } catch (InterruptedException e) { ??????????????? // TODO Auto-generated catch block ??????????????? e.printStackTrace(); ??????????? } ??????? } ??????? for(int j=1;j<=10;j++){ ??????????? System.out.println("sub thread sequence of " + j + ",loop of " + i); ??????? } ??????? bShouldSub = false; ??????? this.notify(); ??? } ? ??? public synchronized void main(int i){ ??????? while(bShouldSub){ ??????????? try { ??????????????? this.wait(); ??????????? } catch (InterruptedException e) { ??????????????? // TODO Auto-generated catch block ??????????????? e.printStackTrace(); ??????????? } ??????? } ??????? for(int j=1;j<=100;j++){ ??????????? System.out.println("main thread sequence of " + j + ",loop of " + i); ??????? } ??????? bShouldSub = true; ??????? this.notify(); ??? } } ? |
使用Condition的好處,它可以有多路等待情況,它可以實現我們用Object的wait()和notify()這兩個方法實現不了的功能。下面我們看一個Condition實現多路等待的例子:
這是一個隊列,可阻塞的一個隊列的底層代碼實現。
我們先來了解一下什么叫阻塞隊列:有一個發射機,對外發射信號。這個發射機發射的型號來自于哪里呢?來自于一個共同機,也就是一個電腦吧。電腦它連著發射機,電腦里面有很多的短信,都是通過發射機來發射出去的。那么,這個電腦又是怎么收到這個短信的呢?通過尋呼臺的坐席接聽客戶的電話,然后講客戶要發送的信息通過網絡輸入到電腦。有很多個坐席,電腦收到了很多個留言,它就把這些留言交給發射機一個一個去發,這個電腦系統內部的代碼怎么寫?現在這個發射機對外,1秒鐘可以發射一個短信,現在這1秒中正好來了一句,欸,這個發射機就發射出去了。如果這個一秒鐘來了兩個,這個發射機發射幾個?發一個。還有一個就沒了,你叫我,我是不是正在處理你呀,另外一個人也扔給我一個包,我沒有時間去收它,那怎么辦呢?我們準備一個隊列,說白了就是一個數組,然后人家給我一個呼,我就往隊列里面放,再有一個坐席給我一個呼,我又往隊列里面按順序放。依次這樣操作。我有兩個線程,一個線程是專門對應這些坐席,把他們的尋呼信息收進隊列,我還有一個線程是專門從這個隊列里面取,取出來交給發射機。這個隊列總共有8個格子0-7,我取的話是從第0個格子開始取,然后依次往上增,這時候如果我已經取到了序號為7的格子,取完了,接著,我又開始從序號為0的格子開始取,這時候就要有一個變量,這個變量記錄了正要取得那個格子得序號。接下來我放的時候,剛開始我放的是第0個格子,然后這樣依次往上遞增,當放到序號為7的格子之后,又從新從0號格子開始放,假設這個發射機還沒有把這個第0個格子的信息取走,我怎么辦呢?我就阻塞等在那里,等到他一取走,我馬上就放進去。這就是阻塞,就是不能繼續往里面放了,我沒地方放了,我在外面等著。暫停,等待發射機發一個,空一個格子出來,空一個格子出來以后,我就可以放進去了。他一空出來,我馬上就放進去,這是系統自動幫我們調度的。我們只要用到相應的技術就行,至于它內部怎么實現,我們并不管。現在,操作系統應該給我們提供解決這種問題的技術。
因為我現在隊列已經滿了,沒有富余的空間放了。取的時候有沒有一種可能,假如我已經放了3個,現在我取到3了,我再接著要取,是不是要取第四個?第四個你放數據沒有?你要沒放,那時候我怎么辦?阻塞,在那里等,一旦你放了一個進來,是不是你正好就放在第四個格子了?我就把它取走了。那我現在為什么要用這個隊列呢?起到緩沖的效果。不是緩存,是緩沖。緩沖區。我們是平均每一秒中發一個短信,我們也計算過,我們的客戶有3000萬,我們是平均每一秒鐘收一個短信,所以是不是正好,我一個發射機就夠呀!平均每一秒鐘收一個,那就意味著有時1秒鐘收6個,有時候3秒鐘內一個都沒收到。但是平均下來,1秒鐘一個。這時候進入這個緩沖區,你這一秒鐘來了3個,沒關系,我放在這個隊列里面,下一秒鐘沒有了,那我在緩沖隊列里面取,慢慢發。這就是緩沖區的作用。如果說來了1萬個,但我現在緩沖區只有8個格子,那就有1萬-8的人在那里阻塞。如果出現這種情況,系統會不會崩?就算不崩,你公司也要崩了。這時候我趕快擴容唄。剛開搞更多的發射機唄。這個什么東西都是有個度的,你只是解決少量的這個問題,如果太大量,這個時候要解決硬件問題了,別指望這個軟件崩不崩了。
那下面看,人家給我們實現了這么一個可阻塞的隊列。隊列,先放進來的先取走。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; ? class BoundedBuffer { ??? final Lock lock = new ReentrantLock(); ? ??? final Condition notFull = lock.newCondition(); ? ??? final Condition notEmpty = lock.newCondition(); ? ??? final Object[] items = new Object[100];//隊列里面1最多裝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;//每放一個就把count給++,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;//每取走一個就把count給-- ??????????? notFull.signal(); ??????????? return x; ??????? } finally { ??????????? lock.unlock(); ??????? } ??? } } |
用一個Condition也能實現功能,為什么代碼里面要用兩個Condition?既然它用了兩個,這里面一定是有原因的。如果我們只用一個Condition到底會出現什么問題?假如我現在有5個線程同時在往緩沖區里面放數據,發現緩沖區已經滿了,這時,這五個線程都在放數據的方法阻塞。結果,這時有一個線程去取數據,取完了數據就通知其它線程中的一個喚醒。這五個等待放數據的線程中有一個被喚醒了,喚醒了它就去放數據,放完數據之后,他又發一個通知去喚醒正在等待的線程中的某個蘇醒,那么現在問題來了,他本來是要通知取的線程,說,我已經放完了,你現在可以取了。但是由于放數據和取數據用的是同一個Condition通信,此時等待通知的線程里面除了那個取數據的線程,還有4個放數據的線程,結果它這個喚醒通知沒有通知到取數據的那個哥們,而是同時到了放數據的4個里面的一個,其實剛才他放數據的時候就又把緩沖區給放滿了,此時這個被喚醒的放數據的線程一看緩沖區是滿的,只好又繼續阻塞等待。所以,如果有兩個Condition的話們就可以把放數據和取數據的線程區分開來,一個只用來喚醒放數據的線程放數據,一個只用來喚醒取數據的線程取數據。通過這樣,我們就自圓其說了。
根據我們上面的理解,下面我們來寫一個3個Condition通信的代碼:
public class ThreeConditionCommunication { ? ??? /** ???? * @param args ???? */ ??? public static void main(String[] args) { ? ??????? final Business business = new Business(); ??????? new Thread(new Runnable() { ? ??????????? @Override ??????????? public void run() { ? ??????????????? for (int i = 1; i <= 50; i++) { ??????????????????? business.sub2(i); ??????????????? } ? ??????????? } ??????? }).start(); ? ??????? new Thread(new Runnable() { ? ??????????? @Override ??????????? public void run() { ? ??????????????? for (int i = 1; i <= 50; i++) { ??????????????????? business.sub3(i); ??????????????? } ? ??????????? } ??????? }).start(); ? ??????? for (int i = 1; i <= 50; i++) { ??????????? business.main(i); ??????? } ? ??? } ? ??? static class Business { ??????? Lock lock = new ReentrantLock(); ??????? //規則,main喚醒sub2,sub2喚醒shub3,sub3喚醒main。所以我們定義3個condition各司其職。 ??????? Condition condition1 = lock.newCondition();//用于控制main的condition ? ??????? Condition condition2 = lock.newCondition();//用于控制sub2的condition ? ??????? Condition condition3 = lock.newCondition();//用于控制sub3的condition ? ??????? private int shouldSub = 1; ? ??????? public void sub2(int i) { ??????????? lock.lock(); ??????????? try { ??????????????? while (shouldSub != 2) { ??????????????????? try { ??????????????????????? condition2.await(); ??????????????????? } catch (Exception e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????????? for (int j = 1; j <= 10; j++) { ??????????????????? System.out.println("sub2 thread sequence of " + j + ",loop of " + i); ??????????????? } ??????????????? shouldSub = 3; ??????????????? condition3.signal(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ? ??????? public void sub3(int i) { ??????????? lock.lock(); ??????????? try { ??????????????? while (shouldSub != 3) { ??????????????????? try { ??????????????????????? condition3.await(); ??????????????????? } catch (Exception e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????????? for (int j = 1; j <= 20; j++) { ??????????????????? System.out.println("sub3 thread sequence of " + j + ",loop of " + i); ??????????????? } ??????????????? shouldSub = 1; ??????????????? condition1.signal(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ? ??????? public void main(int i) { ??????????? lock.lock(); ??????????? try { ??????????????? while (shouldSub != 1) { ??????????????????? try { ??????????????????????? condition1.await(); ??????????????????? } catch (Exception e) { ??????????????????????? // TODO Auto-generated catch block ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? } ??????????????? for (int j = 1; j <= 100; j++) { ??????????????????? System.out.println("main thread sequence of " + j + ",loop of " + i); ??????????????? } ??????????????? shouldSub = 2; ??????????????? condition2.signal(); ??????????? } finally { ??????????????? lock.unlock(); ??????????? } ??????? } ? ??? } } |
?[z1]線程進來是來讀數據的,所以,先拿了一個讀鎖鎖上,這個讀鎖它并不排斥其它的線程來拿,大家可以同時拿讀鎖。
?[z2]這個時候,上面的if語句檢查到并沒有數據可讀,所以接下來,這個線程準備進行寫操作,這時候它釋放了之前拿到的讀鎖,準備去拿寫鎖。
?[z3]線程拿了一把寫鎖,寫鎖是互斥的,同一個寫鎖同一時間只能有一個線程持有。
?[z4]注意這段代碼:它掛上了一把讀鎖準備去讀數據,在讀數據之前,它要先把之前持有的寫鎖釋放掉。我們的疑惑是,寫鎖里面是不能有讀鎖的,因為寫的時候是不允許讀的。解釋:我自己掛的寫鎖,我還是可以掛讀鎖的,也就說,當一個線程拿到了寫鎖之后,這個線程還可以繼續去拿讀鎖。鎖是用來跟別的線程互斥的,是用來擋別人的,不是用來擋自己的。它其實是這樣的,我在釋放寫鎖前去掛讀鎖,就會把寫鎖降級,降級成為更新鎖。更新鎖只有當前線程才可以掛,別人掛不上的。