Java并發

無狀態對象一定是線程安全的

競態條件
當某個計算的正確性取決于多個線程的交替執行時序時,那么久會發生競態條件。換句話說,就是正確的結果要取決于運氣。最常見的競態條件類型就是“先檢查后執行”操作,即通過一個可能失效的觀測結果來決定下一步的動作。

當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值
理解volatile變量的一種有效方法是,將volatile變量的讀操作和寫操作分別替換成get和set方法。然而,在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比synchronized關鍵字更輕量級的同步機制

加鎖機制既可以確保可見性又可以確保原子性,而volatile變量只能確保可見性

當且僅當滿足以下所有條件時,才應該使用volatile變量

  • 對變量的寫入操作不依賴變量當前值,或者能確保只有單個線程更新變量的值
  • 該變量不會與其他狀態變量一起納入不變性條件中
  • 在訪問變量時不需要加鎖

Java并發性和多線程介紹目錄

線程安全及不可變性

當多個線程同時訪問同一個資源,并且其中的一個或者多個線程對這個資源進行了寫操作,才會產生競態條件。多個線程同時讀同一個資源不會產生競態條件。
我們可以通過創建不可變的共享對象來保證對象在線程間共享時不會被修改,從而實現線程安全。

“不變”(immutable)和“只讀”(Read Only)是不同的,當一個變量是“只讀”時,變量的值不能直接改變,但是可以在其他變量發生改變的時候發生改變。

引用不是線程安全的
即使一個對象是線程安全的不可變對象,指向這個對象的引用也可能不是線程安全的

線程的創建方式

Java 提供了三種創建線程的方法:

線程池實現

【Java并發編程】并發編程大合集

  1. 使用wait/notify/notifyAll實現線程間通信的幾點重要說明
    在Java中,可以通過配合調用Object對象的wait()方法和notify()方法或notifyAll()方法來實現線程間的通信。在線程中調用wait()方法,將阻塞等待其他線程的通知(其他線程調用notify()方法或notifyAll()方法),在線程中調用notify()方法或notifyAll()方法,將通知其他線程從wait()方法處返回。

如果線程調用了對象的wait()方法,那么線程便會處于該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
當有線程調用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機喚醒一個wait線程),被喚醒的線程變回進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。
優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用wait()方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了synchronized代碼塊,它會釋放該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

  1. 線程間通信中notify通知的遺漏(含代碼)
    notify通知的遺漏很容易理解,即threadA還沒開始wait的時候,threadB已經notify了,這樣,threadB通知是沒有任何響應的,當threadB退出synchronized代碼塊后,threadA再開始wait,便會一直阻塞等待,直到被別的線程打斷。

在使用線程的等待/通知機制時,一般都要配合一個boolean變量值(或者其他能夠判斷真假的條件),在notify之前改變該boolean變量的值,讓wait返回后能夠退出while循環(一般都要在wait方法外圍加一層while循環,以防止早期通知),或在通知被遺漏后,不會被阻塞在wait方法處。這樣便保證了程序的正確性。

  1. 線程間通信中notifyAll造成的早期通知問題(含代碼)
    如果線程在等待時接到通知,但線程等待的條件還不滿足,此時,線程接到的就是早期通知,如果條件滿足的時間很短,但很快又改變了,而變得不再滿足,這時也將發生早期通知。wait的線程被notif之后從wait之后的代碼開始執行,所以進入wait的條件有可能發生了改變,需要用while來判斷

在使用線程的等待/通知機制時,一般都要在while循環中調用wait()方法,滿足條件時,才讓while循環退出,這樣一般也要配合使用一個boolean變量(或其他能判斷真假的條件,如本文中的list.isEmpty()),滿足while循環的條件時,進入while循環,執行wait()方法,不滿足while循環的條件時,跳出循環,執行后面的代碼。

  1. 并發新特性—Lock鎖和條件變量(含代碼)
    • 簡單實用Lock鎖

    Java 5中引入了新的鎖機制--java.util.concurrent.locks中的顯式的互斥鎖:Lock接口,它提供了比synchronized更加廣泛的鎖機制。
    Lock接口有3個實現類:ReentrantLock、ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock,即重入鎖,讀鎖和寫鎖
    Lock必須被顯式的創建、鎖定和釋放

  • ReentrantLock和synchronized比較

ReentrantLock相對synchronized增加了一些高級功能,如下:

  1. 等待可中斷:當持有鎖的線程長期不釋放鎖時,正在等待的線程可以選擇放棄等待,改為處理其他事情。而在等待由synchronized產生的互斥鎖,會一直阻塞,是不能被中斷
  2. 可實現公平鎖:多個線程在等待同一個鎖時,必須按照申請鎖的時間順序排隊等待,而非公平鎖則不保證這點,在鎖釋放時,任何一個等待鎖的線程都有機會獲得鎖。synchronized中的鎖是非公平鎖,ReentrantLock默認情況下也是非公平鎖,但可以通過構造方法ReentrantLock(true)來要求使用公平鎖
  3. 鎖可以綁定多個條件:ReentrantLock對象可以同時綁定多個Condition對象(條件變量或者條件隊列)
  • 讀寫鎖

synchronized獲得互斥鎖不僅互斥讀寫操作、寫寫操作,還互斥讀讀操作,而讀讀操作時不會帶來數據競爭,因此對讀讀操作也互斥的話會降低性能。Java 5中提供了讀寫鎖,它將讀鎖和寫鎖分離,使得讀讀操作不互斥,獲取讀鎖和寫鎖的一般形式如下:
ReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock(); //獲取寫鎖
rwl.readLock().lock(); //獲取讀鎖

  • 使用ReentrantLock的最佳時機:當你需要以下高級特性時,才應該使用:可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列或者非塊結構的鎖。否則,請使用synchronized

Java中的可重入(Reentrant)鎖

  • 可重入鎖是一種特殊的互斥鎖,它可以被同一個線程多次獲取,而不會產生死鎖

    1. 首先它是互斥鎖:任意時刻,只有一個線程鎖。即假如A線程已經獲取了鎖,在A線程釋放這個鎖之前,B線程是無法獲取這個鎖的,B要獲取這個鎖就要進入阻塞狀態
    2. 其次,它可以被同一個線程多次持有。即,假如A線程已經獲取了這個鎖,如果A線程在釋放鎖之前又一次請求獲取這個鎖,那么是能夠成功的
  • Java中的可重入鎖

    1. synchronized
    synchronized是Java提供的內置鎖,是互斥鎖,又是可重入鎖
    synchronized關鍵字有三種用法:
    * 加在對象方法上:鎖住的是當前對象,不同的對象可以被同時訪問
    * 加在類方法上:鎖住的是當前類對應在JVM中的Class對象
    * 加在代碼塊上:鎖住的是synchronized(lockobj)中的lockobj

2. ReentrantLock
ReentrantLock是java提供的顯示鎖,它也是互斥鎖,也是可重入鎖

  • 總結
  • 可重入鎖:即某個線程獲得了鎖之后,在鎖釋放前,它可以多次重新獲取該鎖
  • 可重入鎖解決了重入鎖死的問題
  • Java的內置鎖synchronized和ReentrantLock都是可重入鎖

自旋鎖


ReentrantLock實現了Lock接口,并提供了與synchronized相同的互斥性和內存可見性。

40個Java多線程問題總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容