Java5線程并發庫之LOCK(鎖)CONDITION(條件)實現線程同步通信

為什么80%的碼農都做不了架構師?>>> ??hot3.png

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方法去查詢數據庫,如果查到就賦值給那個真實對象的引用變量,并返回,沒查到就拋出異常。

180237_ZbeA_3512041.png

好了,明白上面的思想,我們再來看下面的例子。

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的人在那里阻塞。如果出現這種情況,系統會不會崩?就算不崩,你公司也要崩了。這時候我趕快擴容唄。剛開搞更多的發射機唄。這個什么東西都是有個度的,你只是解決少量的這個問題,如果太大量,這個時候要解決硬件問題了,別指望這個軟件崩不崩了。

180321_Wkbp_3512041.png

那下面看,人家給我們實現了這么一個可阻塞的隊列。隊列,先放進來的先取走。

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]注意這段代碼:它掛上了一把讀鎖準備去讀數據,在讀數據之前,它要先把之前持有的寫鎖釋放掉。我們的疑惑是,寫鎖里面是不能有讀鎖的,因為寫的時候是不允許讀的。解釋:我自己掛的寫鎖,我還是可以掛讀鎖的,也就說,當一個線程拿到了寫鎖之后,這個線程還可以繼續去拿讀鎖。鎖是用來跟別的線程互斥的,是用來擋別人的,不是用來擋自己的。它其實是這樣的,我在釋放寫鎖前去掛讀鎖,就會把寫鎖降級,降級成為更新鎖。更新鎖只有當前線程才可以掛,別人掛不上的。

轉載于:https://my.oschina.net/kangxi/blog/1822037

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/451619.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451619.shtml
英文地址,請注明出處:http://en.pswp.cn/news/451619.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

互聯網,可預見的未來

我記憶中的1998年代&#xff0c;PC迅猛發展&#xff0c;CPU速度逐年翻番&#xff0c;持續了7年&#xff0c;但下一個7年到現在&#xff0c;基本上沒有太大提升&#xff1b;顯示器從14英寸CRT發展到2005的21英寸LED&#xff0c;后來也沒有繼續進化。為什么&#xff1f;當人對計算…

什么時候用GET?什么時候用POST?

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 一、 GET和POST兩種方法都是將數據送到服務器&#xff0c;但你該用哪一種呢&#xff1f; HTTP標準包含這兩種方法是為了達到不同的目的…

邏輯運算符與邏輯表達式

1 #include <stdio.h>2 3 int main()4 {5 int a0;int b0;6 if(a&&b)//a&&ba的邏輯值為0&#xff0c;則執行else7 {8 printf("a&&b is true\n");9 } 10 else 11 { 12 printf("a&&…

linux/shell相關知識點

阿里Linux Shell腳本面試25個經典問答 Linux運維工程師12道面試題整理 感謝作者分享&#xff01;

20180601]函數與標量子查詢2.txt

[20180601]函數與標量子查詢2.txt --//昨天看http://www.cnblogs.com/kerrycode/p/9099507.html鏈接,里面提到: 通俗來將&#xff0c;當使用標量子查詢的時候&#xff0c;ORACLE會將子查詢結果緩存在哈希表中&#xff0c; 如果后續的記錄出現同樣的值&#xff0c;優化器通過緩存…

ODP 使用 ArrayBind 時可能會遇到的巨坑 'System.IConvertible' 的解決方法

Unable to cast object of type System.Nullable1[System.Int16][] to type System.IConvertible 一段代碼99%不會出錯&#xff0c;0.1%會報上邊的錯&#xff0c;debug費了老鼻子時間&#xff0c;發現此坑很深。異常是 cmd.ExecuteNonQuery() 拋的&#xff0c;實際是 para.Valu…

eclipse快速定位到錯誤處

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程 以前都是按著滾動條往下拉&#xff0c;找到錯誤的地方&#xff0c;有時比較多的時候就很麻煩。 其實eclipse是可以直接快速定位的&#x…

C語言中的“”和“”

先說左移,左移就是把一個數的所有位都向左移動若干位,在C中用<<運算符.例如: int i 1; i i << 2; //把i里的值左移2位 也就是說,1的2進制是000...0001(這里1前面0的個數和int的位數有關,32位機器,gcc里有31個0),左移2位之后變成 000...0100,也就是10進制的4,所以…

網站性能優化的三重境界

這篇文章是關于網站性能優化體驗的&#xff0c;性能優化是一個復雜的話題&#xff0c;牽涉的東西非常多&#xff0c;我只是按照我的理解列出了性能優化整個過程中需要考慮的種種因素。點到為止&#xff0c;包含的內容以淺顯的介紹為主&#xff0c;如果你有見解能告知我那再好不…

Linux使用RSA實現免密登錄(原理)

參考文獻Linux密鑰rsa加密原理和ssh使用密鑰實現免密碼登錄 感謝作者分享&#xff01;

PYTHON 爬蟲筆記十一:Scrapy框架的基本使用

Scrapy框架詳解及其基本使用 scrapy框架原理 Scrapy是一個為了爬取網站數據&#xff0c;提取結構性數據而編寫的應用框架。 其可以應用在數據挖掘&#xff0c;信息處理或存儲歷史數據等一系列的程序中。其最初是為了頁面抓取 (更確切來說, 網絡抓取 )所設計的&#xff0c; 也可…

java設計把兩個字符串的值交換 而不使用中間變量

public class Test {public static void main(String[] args) {String s1 "aaa";String s2 "cccx";s1 s1 s2;s2 s1.substring(0, s1.length()-s2.length());s1 s1.substring(s2.length());System.out.println(s1" - "s2);}}

服務器返回值 解釋 ajax提交方式 后臺數據刷進前端

轉載于:https://www.cnblogs.com/liuliang389897172/p/9120715.html

no typehandler found for property XXXX 解決

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. ssm框架下 啟動服務報錯如題。 2. 原因&#xff1a; 我的情況是&#xff0c;代碼中實體屬性映射書寫和數據庫字段名字不一致。 數據…

C++主流預處理,編譯和鏈接過程

在C的程序的編寫過程中&#xff0c;基本上都碰到過LNK2005的錯誤吧&#xff0c;下面就針對這個問題詳細分析&#xff1a;首先&#xff0c;預處理階段&#xff1a;這一過程&#xff0c;主要針對#include和#define進行處理&#xff0c;具體過程如下&#xff1a;對于cpp文件中經常…

shell中sed -i特殊字符

可參考文獻&#xff1a; Linux生產環境上&#xff0c;最常用的一套“sed“技巧 看懂shell中的各種語句

Win10遠程桌面提示你的憑據不工作的處理方法

需要確保在組策略編輯器&#xff08;WinR 輸入 gpedit.msc &#xff09;中計算機配置->Windows設置->安全設置->本地策略->安全選項->右側的網絡訪問:本地帳戶的共享和安全模型。修改為使用經典模式即可&#xff01;

子網掩碼255.255.0.0與255.255.255.0的區別

先介紹子網掩碼&#xff1a;子網掩碼&#xff0c;是一種用來指明一個IP地址的哪些位標識的是主機所在的子網&#xff0c;以及哪些位標識的是主機的位掩碼。子網掩碼不能單獨存在&#xff0c;它必須結合IP地址一起使用。子網掩碼只有一個作用&#xff0c;就是將某個IP地址劃分成…

日期格式不符合要求:Unparseable date: quot;3e8a4d83533744c698216535a65850c0quot;

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. 報錯如題 2. 原因&#xff1a;使用token 記錄當前登陸用戶&#xff0c;token值已經過期。 HttpClientUtil.doPost&#xff08;&…

Spring中都用到了哪些設計模式?

Spring 框架中用到了哪些設計模式&#xff1a; 工廠設計模式 : Spring使用工廠模式通過 BeanFactory、ApplicationContext 創建 bean 對象。 代理設計模式 : Spring AOP 功能的實現。 單例設計模式 : Spring 中的 Bean 默認都是單例的。 模板方法模式 : Spring 中 jdbcTemplat…