1. 程式人生 > >java執行緒安全的實現方法_筆記

java執行緒安全的實現方法_筆記

閱讀《深入理解java虛擬機器》後的閱讀筆記。

1、 互斥同步

同步時指在多個執行緒併發訪問共享資料時,保證共享資料在同一個時刻只被一個(或者是一些,使用訊號量的時候)執行緒使用。而互斥是實現同步的一種手段,臨界區,互斥量和訊號量都是主要的互斥實現方式。

  1.1 在java中最基本的互斥同步手段就是synchronized關鍵字,synchronized關鍵字經過編譯之後,會在同步塊的前後分別形成monitorenter和monitorexit這兩個位元組碼指令。根據虛擬機器規範要求,在執行monitorenter指令時,首先要嘗試獲取物件的鎖。如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件的鎖,把鎖的計數加1,相應的,在執行monitorexit指令時會將鎖計數器減1,當計數器為0時,鎖就被釋放。如果獲取物件鎖失敗,那當前執行緒就要阻塞等待,直到物件鎖被另外一個執行緒釋放為止。

注意:

因為java的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一個執行緒,都需要作業系統來幫忙完成,這就需要從使用者態轉換到核心態中,因此狀態轉換需要耗費很多的處理器時間。多以synchronized是java語言中一個重量級(Heavyweight)的操作。

  1.2 使用java.util.concurrent包中的重入鎖(ReentrantLock)來實現同步。相比synchronized,ReentrantLock增加了一些高階功能,主要有以下三項:

     等待可中斷:當持有鎖的執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待,改為處理其他事情

     可實現公平鎖:多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖

     鎖可以繫結多個條件:一個ReentrantLock物件可以同時繫結多個Condition物件

2、 非阻塞同步

互斥同步最主要的問題就是進行執行緒阻塞和喚醒所帶來的效能問題,這是一種悲觀的併發策略。總是認為只要不去做正確的同步措施(加鎖),那就肯定會出問題,無論共享資料是否真的會出現競爭,它都要進行加鎖、使用者態核心態轉換、維護鎖計數器和檢查是否有被阻塞的執行緒需要被喚醒等操作。

隨著硬體指令集的發展,我們可以使用基於衝突檢測的樂觀併發策略。先進行操作,如果沒有其他執行緒徵用資料,那操作就成功了;如果共享資料有徵用,產生了衝突,那就再進行其他的補償措施。這種樂觀的併發策略的許多實現不需要執行緒掛起,所以被稱為非阻塞同步。

3、無同步方案

  3.1 可重入程式碼:這種程式碼也叫做純程式碼(Pure Code),可以在程式碼執行的任何時刻中斷他,轉而去執行另外一段程式碼(包括遞迴呼叫它本身),它可以保證原來的程式不會出現任何錯誤。

  3.2 執行緒本地儲存:如果一段程式碼中所需要的資料必須與其他程式碼共享,那就看看這些共享資料的程式碼是否能保證在同一個執行緒中執行?如果能保證,我們就可以把共享資料的課件範圍限制在同一個執行緒之內,這樣,無須同步也能保證執行緒之間不出現資料爭用問題。(例如:“生產者-消費者”模式,都會將產品的消費過程儘量在一個執行緒中消費完)