java并發(fā)入門

一、進(jìn)程

在多進(jìn)程設(shè)計(jì)中各進(jìn)程之間的數(shù)據(jù)塊是相互獨(dú)立,彼此通過信號、管道進(jìn)行通信。而在多線程設(shè)計(jì)中,各線程不一定獨(dú)立,同一任務(wù)中的各線程共享程序段、數(shù)據(jù)段等資源。Java通過Package類(Java.lang.package)支持多進(jìn)程,通過Thread類支持多線程。

二、線程

多線程既有生產(chǎn)者消費(fèi)者,哲學(xué)家就餐,讀寫器或者簡單的有界緩沖區(qū)等應(yīng)用問題。也有死鎖,競態(tài)條件,內(nèi)存沖突和線程安全等并發(fā)問題。
為方便寫出線程安全的程序,Java提供線程安全類和并發(fā)工具,比如:同步容器、并發(fā)容器、隊(duì)列、同步工具類。

三、多線程環(huán)境解決方案及原理

基本上所有的并發(fā)模式都是采用串行訪問共享資源的方案解決線程沖突問題。

Java語言的同步機(jī)制,在底層實(shí)現(xiàn)有兩種方式:互斥協(xié)同。在語言層面,就是內(nèi)置鎖synchronized內(nèi)置條件隊(duì)列,即Object的wait(),notify(),notifyAll()方法。
顯式鎖為Lock,顯示條件隊(duì)列為Condition對象。

@TreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{
public BoundedBuffer(int size){
    super(size);
}
public synchronized void put(V v) throws InterruptedException{
    while ( isFull){
        wait();
    }
    doPut(v);
    notifyAll();
}
public  synchronized V take() throws InterruptedException{
    while( isEmpty()){
        wait();
    }
    V v = doTake();
    notifyAll();
    retur v;
}
}

四、鎖

synchronized

在Java語言中有兩種內(nèi)建的synchronized語法:synchronized語句、synchronized方法。

synchronized語句:當(dāng)源代碼被編譯成字節(jié)碼的時(shí)候,會(huì)在同步塊的入口位置和出口分別插入monitorenter和monitorexit字節(jié)碼指令。
synchronized方法:在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位設(shè)置為1。

每個(gè)對象都有一個(gè)鎖,也就是監(jiān)視器(monitor)。當(dāng)monitor被占有時(shí)表示它被鎖定。線程執(zhí)行monitorenter指令時(shí)嘗試獲取對象所對應(yīng)的monitor的所有權(quán)。

相同點(diǎn):Lock能完成synchronized的相同功能。

synchronized java.util.concurrent.locks.Lock
原理 在對象頭設(shè)置標(biāo)記 Lock接口的實(shí)現(xiàn)類只用volatile修飾的int變量保證每個(gè)線程都擁有對該int的可見性和原子修改。
自動(dòng)釋放鎖 必需手工在finally從句中釋放。
可 定時(shí),中斷、公平鎖、非阻塞
ReentrantLock

ReentrantLock利用CAS+CLH隊(duì)列來實(shí)現(xiàn)。支持公平鎖和非公平鎖。
CAS:Compare and Swap,比較并交換。CLH隊(duì)列:帶頭結(jié)點(diǎn)的雙向非循環(huán)鏈表。

ReentrantLock的實(shí)現(xiàn):先通過CAS嘗試獲取鎖。如果鎖已經(jīng)被占據(jù),就加入CLH隊(duì)列并被掛起。當(dāng)鎖被釋放后,排在CLH隊(duì)首的線程會(huì)被喚醒,然后CAS再次嘗試獲取鎖。在這個(gè)時(shí)候,非公平鎖:如果同時(shí)還有另一個(gè)線程進(jìn)來嘗試獲取,那么有可能會(huì)讓這個(gè)線程搶先獲取。公平鎖:如果同時(shí)還有另一個(gè)線程進(jìn)來嘗試獲取,當(dāng)它發(fā)現(xiàn)自己不是在隊(duì)首的話,就會(huì)排到隊(duì)尾,由隊(duì)首的線程獲取到鎖。

Condition

Condition和Lock關(guān)聯(lián)使用,就像條件隊(duì)列和內(nèi)置鎖相關(guān)聯(lián)一樣。在相關(guān)聯(lián)的Lock上調(diào)用Lock.newCondition()創(chuàng)建Condition。
Condition比內(nèi)置條件隊(duì)列提供更豐富的功能:在每個(gè)鎖上可存在多個(gè)等待,條件等待是可中斷的或者不可中斷的、基于時(shí)限的,以及公平的或非公平的隊(duì)列操作。
與內(nèi)置條件隊(duì)列不同的是,對于每個(gè)Lock,可以有任意數(shù)量的Condition對象。Condition對象繼承了相關(guān)的Lock對象的公平性,對于公平的鎖,線程會(huì)依照FIFO順序從Condition.await中釋放。
注意:在Condition對象中,與wait,notify和notifyAll方法對應(yīng)的分別是await,signal,signalAll。但是,Condition繼承了Object,因而它也包含wait和notify方法。一定要確保使用的版本——await和signal。

鎖的高級特性

可重入:線程的可重入,是指外層函數(shù)獲得鎖之后,內(nèi)層也可以獲得鎖。ReentrantLock和synchronized都是可重入鎖。
驚群效應(yīng)(Herd Effect):占有鎖的線程釋放后,所有等待獲取鎖的競爭者同時(shí)被喚醒,都嘗試搶占鎖。
公平鎖和非公平鎖:非公平鎖普遍比公平鎖開銷小。但如果必須要鎖的競爭者按順序獲得鎖,那么就需要實(shí)現(xiàn)公平鎖。
阻塞鎖和自旋鎖:阻塞鎖會(huì)有上下文切換,如果并發(fā)量比較高且臨界區(qū)的操作耗時(shí)比較短,那么造成的性能開銷就比較大。如果臨界區(qū)操作耗時(shí)比較長,一直保持自旋,也會(huì)對CPU造成更大的負(fù)荷。

鎖的對象

實(shí)例同步方法,鎖是當(dāng)前實(shí)例對象。
靜態(tài)同步方法,鎖是當(dāng)前對象的Class對象。
同步方法塊,鎖是synchonized括號里的對象。

死鎖

死鎖是指多個(gè)線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。必須滿足以下四個(gè)條件才發(fā)生死鎖:

  • 互斥:一個(gè)資源每次只能被一個(gè)線程使用。
  • 請求與保持:一個(gè)線程因請求資源而阻塞時(shí),不釋放已獲得的資源。
  • 不剝奪:線程已獲得的資源,在末使用完之前,不能被強(qiáng)行剝奪。
  • 循環(huán)等待:若干線程之間形成循環(huán)等待資源關(guān)系。

避免死鎖最簡單的方法就是阻止循環(huán)等待條件,將系統(tǒng)中所有的資源設(shè)置標(biāo)志位并排序,規(guī)定所有的進(jìn)程申請資源必須按順序進(jìn)行。
避免死鎖的通用經(jīng)驗(yàn)法則是:當(dāng)要訪問共享資源A、B、C 時(shí),保證每個(gè)線程都按同樣的順序訪問共享資源。

活鎖

處于活鎖的線程的狀態(tài)是不斷改變的,活鎖可以認(rèn)為是一種特殊的饑餓。一個(gè)現(xiàn)實(shí)的活鎖例子是兩個(gè)人在走廊碰到,兩個(gè)人都試著避讓對方好讓彼此通過,但是因?yàn)楸茏尩姆较蚨家粯訉?dǎo)致最后誰都不能前進(jìn)。

饑餓
鎖降級

鎖降級是指寫鎖降級成讀鎖。如果當(dāng)前線程擁有寫鎖,然后將其釋放,最后獲取讀鎖,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持?。ó?dāng)前擁有的)寫鎖,再獲取到讀鎖,最后釋放(先前擁有的)寫鎖的過程。
鎖降級中的讀鎖是否有必要呢?答案是必要。主要是為了保證數(shù)據(jù)的可見性,如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖,假設(shè)此刻另一個(gè)線程(T)獲取了寫鎖并修改了數(shù)據(jù),那么當(dāng)前線程無法感知線程T的數(shù)據(jù)更新。如果當(dāng)前線程獲取讀鎖,即遵循鎖降級的步驟,則線程T將會(huì)被阻塞,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫鎖進(jìn)行數(shù)據(jù)更新。

讀寫鎖ReentrantReadWriteLock

讀寫鎖有兩個(gè)鎖,一個(gè)是讀操作相關(guān)的鎖,稱為共享鎖;另一個(gè)是寫操作相關(guān)的鎖,也叫排它鎖。多個(gè)讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。讀寫鎖是用來提升并發(fā)程序性能的鎖分離技術(shù)的成果。

悲觀鎖

悲觀鎖假設(shè)最壞的情況,并且只有在確保其他線程不會(huì)干擾(通過獲取正確的鎖)的情況下才能執(zhí)行下去。
常見實(shí)現(xiàn)如獨(dú)占鎖。
安全性高,但在中低并發(fā)程度下的效率低。

樂觀鎖

樂觀鎖借助沖突檢查機(jī)制來判斷在更新過程中是否存在其他線程的干擾,如果存在,這個(gè)操作將失敗,并且可以重試(也可以不重試)。
常見實(shí)現(xiàn)如CAS等。
部分樂觀鎖削弱了一致性,但提高了中低并發(fā)程度下的效率。

五、java線程間通信之條件隊(duì)列

條件隊(duì)列中存儲的是"處于等待狀態(tài)的線程",這些線程在等待某種條件變成真。正如每個(gè)Java對象都可以作為一個(gè)鎖,每個(gè)對象同樣可以作為一個(gè)條件隊(duì)列,這個(gè)對象的wait,notify,notifgAll就構(gòu)成了內(nèi)部條件隊(duì)列的API。對象的內(nèi)置鎖與條件隊(duì)列是相互關(guān)聯(lián)的。要調(diào)用條件隊(duì)列的任何一個(gè)方法,必須先持有該對對象上的鎖。

"條件隊(duì)列中的線程一定是執(zhí)行不下去了才處于等待狀態(tài)",這個(gè)"執(zhí)行不下去的條件"叫做"條件謂詞"。

wait()方法的返回并不一定意味著正在等待的條件謂詞變成真了。
舉個(gè)列子:假設(shè)現(xiàn)在有三個(gè)線程在等待同一個(gè)條件謂詞變成真,然后另外一個(gè)線程調(diào)用了notifyAll()方法。此時(shí),只能有一個(gè)線程離開條件隊(duì)列,另外兩個(gè)線程將仍然需要處于等待狀態(tài),這就是在代碼中使用while(conditioin is not true){this.wait();}而不使用if(condition id not true){this.wait();}的原因。
另外一種情況是:同一個(gè)條件隊(duì)列與多個(gè)條件謂詞互相關(guān)聯(lián)。這個(gè)時(shí)候,當(dāng)調(diào)用此條件隊(duì)列的notifyAll()方法時(shí),某些條件謂詞根本就不會(huì)變成真。

在本文的例子中,可以看到使用while而不是if來判斷條件謂詞是否為空,就是基于以上幾種原因的考慮。用一句話來概括:“每當(dāng)線程被從wait中喚醒時(shí),都必須再次測試條件謂詞”,切記,下面是條件等待的標(biāo)準(zhǔn)形式:

void xxxMethod() throws InterruptedException{  
    synchronized(lock){ 
        while(!conditionPredition)  
            lock.wait();  
        doSomething();  
    }  
}  

六、線程

終止線程的三種方法

  • 使用volatile 布爾變量作為退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止。
  • 使用stop方法強(qiáng)行終止線程,不推薦此方法,因?yàn)閟top和suspend及resume都是作廢過期的方法,使用它們可能造成數(shù)據(jù)狀態(tài)不一致。
  • 使用interrupt方法中斷線程。(推薦

線程狀態(tài)

  1. 新建狀態(tài): 用new語句創(chuàng)建的線程對象處于新建狀態(tài),此時(shí)它和其它的java對象一樣,僅在堆中被分配了內(nèi)存。
  2. 就緒狀態(tài)(New): 創(chuàng)建了線程后,調(diào)用它的start()方法,該線程就進(jìn)入就緒狀態(tài)。處于這個(gè)狀態(tài)的線程位于可運(yùn)行池中,等待獲得CPU的使用權(quán)。
  3. 運(yùn)行狀態(tài)(Runnable): 處于這個(gè)狀態(tài)的線程占用CPU,執(zhí)行程序的代碼。
  4. 阻塞狀態(tài)(Blocked): 當(dāng)線程處于阻塞狀態(tài)時(shí),java虛擬機(jī)不會(huì)給線程分配CPU,直到線程重新進(jìn)入就緒狀態(tài),它才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。

    阻塞狀態(tài)分為三種情況
    1) 位于對象等待池中的阻塞狀態(tài): 當(dāng)線程運(yùn)行時(shí),如果執(zhí)行了某個(gè)對象的wait()方法,JVM就會(huì)把這個(gè)線程放到這個(gè)對象的等待池中。
    2) 位于對象鎖中的阻塞狀態(tài): 當(dāng)線程運(yùn)行時(shí),試圖獲得某個(gè)對象的同步鎖時(shí),如果該對象的同步鎖已經(jīng)被其他的線程占用,JVM就會(huì)把這個(gè)線程放到這個(gè)對象的瑣池中。
    3) 其它的阻塞狀態(tài): 當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其它線程的join()方法,或者發(fā)出了I/O請求時(shí),就會(huì)進(jìn)入這個(gè)狀態(tài)中。
    Waiting, Time_waiting
    5. 死亡狀態(tài)(Terminated): 當(dāng)線程退出run()方法,就進(jìn)入死亡狀態(tài),該線程結(jié)束了生命周期。要么正常退出、要么遇到異常退出。

如果希望明確地讓一個(gè)線程給另外一個(gè)線程運(yùn)行的機(jī)會(huì),可以采取以下的辦法
1、調(diào)整線程的優(yōu)先級。
2、讓處于運(yùn)行狀態(tài)的線程調(diào)用Thread.sleep()方法,或者調(diào)用Thread.yield()方法,或者調(diào)用另一個(gè)線程的join()方法。

線程中斷

線程中斷是重要的線程協(xié)作機(jī)制。如果在循環(huán)體中,出現(xiàn)類似wait()或者sleep()的操作,則只能通過中斷來識別。

競態(tài)條件

當(dāng)多個(gè)線程同時(shí)修改同一數(shù)據(jù)對象時(shí),可能會(huì)產(chǎn)生不正確的結(jié)果,這時(shí)候就存在一個(gè)競爭條件(race condition)。

如果首先要執(zhí)行的程序競爭失敗排到了后面執(zhí)行,那么整個(gè)程序就會(huì)出現(xiàn)不確定的bugs。這種bugs很難發(fā)現(xiàn)而且會(huì)重復(fù)出現(xiàn),因?yàn)榫€程間是隨機(jī)競爭。

線程通信

Java.lang.Object類中提供兩個(gè)用于線程通信的方法
1、wait(): 線程釋放對象的鎖,JVM會(huì)把該線程放到對象的等待池中。該線程等待其它線程喚醒。
2、notify(): 執(zhí)行該方法的線程喚醒在對象的等待池中等待的一個(gè)線程,JVM從對象的等待池中隨機(jī)選擇一個(gè)線程,把它轉(zhuǎn)到對象的鎖池中。

線程睡眠:當(dāng)線程在運(yùn)行中執(zhí)行了sleep()方法時(shí),會(huì)放棄CPU,轉(zhuǎn)到阻塞狀態(tài),不會(huì)釋放它所持有的鎖。

等待其它線程的結(jié)束:調(diào)用另一個(gè)線程的join()方法,當(dāng)前運(yùn)行的線程將轉(zhuǎn)到阻塞狀態(tài),直到另一個(gè)線程運(yùn)行結(jié)束,它才恢復(fù)運(yùn)行。

join與synchronized的區(qū)別是:join在內(nèi)部使用wait()方法進(jìn)行等待,而synchronized關(guān)鍵字使用的是“對象監(jiān)視器”做為同步。

多線程使用建議

  • 起個(gè)有意義的名字
    方便debug或追蹤。
  • 避免鎖定,縮小同步的范圍
    鎖花費(fèi)的代價(jià)高昂且上下文切換耗費(fèi)時(shí)間空間,試試最低限度的使用同步和鎖,縮小臨界區(qū)。因此相對于同步方法更喜歡同步塊,擁有對鎖的絕對控制權(quán)。
  • 多用同步類,少用wait 和 notify
    CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操作,而用wait和notify很難實(shí)現(xiàn)對復(fù)雜控制流的控制。
  • 多用并發(fā)集合,少用同步集合
    并發(fā)集合比同步集合的可擴(kuò)展性更好。
參考

Java核心技術(shù)點(diǎn)之多線程
JAVA并發(fā)-條件隊(duì)列

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、并發(fā) 進(jìn)程:每個(gè)進(jìn)程都擁有自己的一套變量 線程:線程之間共享數(shù)據(jù) 1.線程 Java中為多線程任務(wù)提供了很多的...
    SeanMa閱讀 2,802評論 0 11
  • 一 .volatile關(guān)鍵字 ?volatile是java提供的輕量級的同步機(jī)制,主要有三個(gè)特性:保證可見性,禁止...
    TianMingforJAVA閱讀 338評論 0 1
  • ?? 本文以及示例源碼已歸檔在 javacore 一、并發(fā)鎖簡介 確保線程安全最常見的做法是利用鎖機(jī)制(Lock、s...
    靜默虛空閱讀 744評論 0 1
  • 上周的面試中,被問及了幾個(gè)并發(fā)開發(fā)的問題,自己回答的都不是很系統(tǒng)和全面,可以說是“頭皮發(fā)麻”,哈哈。因此果斷購入《...
    想象美閱讀 564評論 1 4
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽閱讀 10,805評論 0 11

友情鏈接更多精彩內(nèi)容