1. 程式人生 > >關於執行緒的總結--安全,協調,開銷分析

關於執行緒的總結--安全,協調,開銷分析

執行緒安全幾個焦點:

1.由於CPU的搶佔式排程導致資料的不一致性
2.執行緒死鎖問題
3.執行緒異常退出的處理不當問題
對於作業系統和執行緒執行有一定了解的應該很熟知這三個問題,我不想解釋這三個問題的概念,而是想總結下對這三個問題學到java解決方案。

同步機制

同步機制用於解決第一個問題,有幾種方法可以解決:
其實,所有的手段都使保證一個執行緒的操作的變數和物件不會因為執行緒切換而被搞“髒”。加鎖並阻塞其他競爭執行緒,其實就是告訴別的執行緒,現在我在搞這個物件,你不要插手搗亂,等我弄完給你。

但要注意的是,處在同一競爭條件下的執行緒才會被阻塞。(爭搶同一個物件的控制權的所有執行緒)
1.synchronized關鍵字修飾的域和方法。

synchronized(obj){
    //臨界區操作
    /**
      synchronized使歷史悠久的同步方法,處於這個域中的程式碼要麼全部執行並儲存,要麼全部不執行
    **/
}

方法

public synchronized void getXxx(){
    //臨界區程式碼
    //方法呼叫後將不會產生執行緒級上下文切換(context switch)
}

2.可重入鎖(RetrantLock)

對已經持有的鎖再加n層鎖,這些鎖按照順序全部開啟,競爭阻塞的執行緒才可能獲得鎖的控制權。其實,重新加鎖對實現人員沒什麼好處,就是一個執行緒呼叫加鎖後,呼叫自己的物件方法,而為了保證執行緒安全,這個物件方法也上了鎖,於是鎖加了一層,等方法執行結束後,顯示解鎖,這一層鎖就消失了。

可重入鎖的嘗試加鎖
這是一個很人性化的方案。假如你是一個執行緒,別的執行緒正在持有鎖(你想要物件的控制權),你可以嘗試加鎖,它不給的話,你可以暫時執行一些其他事情。

if(obj.tryLock()){//嘗試加鎖
}
else{
   //高點其他事情
}

我認為這對多核CPU來說才是有用的。因為如果單CPU,持有鎖的執行緒處於臨界區執行,那麼本執行緒想要做其他事情不會被阻塞,也要獲得CPU的排程啊!獲得排程,不意味著效率的提升,這樣做可以防止本執行緒餓死。
然而在多核上,卻可以實際上提高效率,因為執行緒非臨界區操作可以在另一個核上執行。
執行緒安全是一個不能夠被結構化的問題,因為具體業務邏輯應該有不同的加鎖位置,如果粗略畫一個大範圍,加鎖與解鎖距離比臨界區大很多,開銷可是不小啊,同步地代價!
3.終止執行緒引起地執行緒破壞
宣告狼藉地stop和suspend方法


終止執行緒會立即退出執行緒地執行,而退出執行緒的遺留操作弄髒了物件。這就是棄用的原因。而之後版本採用interrupt方法,中斷執行緒只是簡單的將執行緒的中斷狀態位設定為真。中斷是想要終止的請求,並無實際的意義,執行緒通過檢測到中斷位,做一些收尾工作(需要程式設計師速手動處理),然後可以正常退出了。
interrupt涉及的異常interruptedException有典型的兩種情況會被丟擲:
1.等待期間被中斷
2.中斷後,呼叫await(),sleep(),要求執行緒等待的可中斷方法。

主存措施

對主存下手的就是volatile關鍵字,一致性問題存在的關鍵性原因就是現在多核計算機和Cache的寫回策略導致執行緒更改的資訊沒有及時反饋到主存,導致其他執行緒讀到過期資訊。如果你只是希望對一個變數進行更新,那麼volatile的開銷還是比同步鎖小的,但依然比非同步程式碼開銷要大。

volatile會將執行緒的操作的共享域每次更改儲存到主存,主存的訪問要比Cache和暫存器慢多了。好蒼白的解釋,舉個栗子。

兩個執行緒Tfirst,Tsecond分別執行在CPU1CPU2,同時對變數common進行修改。
初始狀態下:主存上存在一個最新值common=initial。由於執行緒執行,common被裝載到CPU1的cache上,接下來CPU2也開始執行。

common更改時間線如下:

CPU1 CPU2 主存 操作描述
initial uninitialed initial comon載入兩個CPU
1 uninitialed initial CPU1更改
1 initial initial CPU2comon

此時出現了不一致性,因為執行寫回策略的cache,會將很多被修改的位元組標誌位已修改,然後批量把修改後的值寫入記憶體。
如果common是用volatile修飾的,那麼CPU1的修改結果會被強制寫入記憶體,來儲存資料一致性。主存寫入是比較慢的,這是用效率來換取安全。(安全性是首要的,所以這是值的)。關於平行計算機,希望可以有些推薦的書籍。

更新日誌:

感覺應該留下一些實際的程式碼或者更多一點比如,條件(condition)什麼的,但又感覺沒什麼必要。關於執行緒池,Future物件這些看起來挺高大上的,不過真的沒有什麼意思,封裝罷了。
也許應該有更精妙的東西,持續攻城中,待更新。2017/11/17

題外話

理解java執行緒底層的構建會對執行緒安全有很大的幫助,當然底層非作業系統層次,而是java對於物件的操作,同步機制。雖然java多執行緒開發,多利用執行緒池等開發人員開發的安全且良好的實現,但討論底層執行緒仍然很有意義。把別人提供的框架當成“黑盒子”,能力永遠無法提高。Alas,越來理解面對如此多的框架和快速開發工具,瞭解底層實現,才是真正的王道。因為作者水平有限,還未完成《java concurrency in practise》的精讀,無法寫出更加透徹的想法和見解,會在後面,重新整理。