1. 程式人生 > >一篇文章總結了JVM執行緒安全

一篇文章總結了JVM執行緒安全

繼上一篇文章的知識點,繼續總結JVM裡面的執行緒安全

一、什麼是執行緒安全

一個老生常談的問題,如果一個物件可以安全的被多個執行緒同時使用,那它就是執行緒安全的。
當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何的其他的協調操作,呼叫這個物件的行為都可以獲得正確的結果,那這個物件是執行緒安全的。

二、執行緒安全程式碼的特徵

程式碼本身封裝了所有必要的正確性保障(如互斥同步等),令呼叫者無須關心多執行緒的問題,更無須自己採取任何措施來保證多執行緒的正確呼叫。

三、JAVA語言中所謂的執行緒安全是什麼?分為什麼程度?

1 不可變物件(絕對安全的)

不可變的物件一定是執行緒安全的。
保證物件行為不影響自己狀態的途徑有很多種,其中最簡單的就是把物件中帶有狀態的變數都宣告為final。
JDK中的不可變物件

  • String
  • Number部分子類
  • Long
  • Double
  • BigInteger
  • BigDecimal

2 絕對執行緒安全

指的是不管執行時環境如何,呼叫者都不需要任何額外的同步措施。
JAVA API 中標註自己是執行緒安全的類,大多數都不是絕對的執行緒安全,因為只是限制在使用某些方法時是原子性操作,但是如果呼叫多個方法,那麼便不是原子性操作了。比如併發去操作Vector安全類,結果卻是錯的,所以不是絕對執行緒安全的。

3 相對執行緒安全

就是我們通常意義上所講的執行緒安全,需要保證對這個物件的單獨操作是執行緒安全的,但是對於一些特定順序的連續呼叫,就可能需要在呼叫端使用額外的同步手段來保證呼叫的正確性。

JDK中的相對執行緒安全類

  • Vector
  • HashTable
  • Collections的synchronizedCollection()方法包裝的集合

4 執行緒相容

是指物件本身並不是執行緒安全的,但是通過呼叫端正確的使用同步手段來保證物件在併發環境中可以安全的使用。

5 執行緒對立

是指無論呼叫端是否採取了同步措施,都無法在多執行緒中併發使用的程式碼。
由於Java語言天生就具備多執行緒特性,執行緒對立這種排斥多執行緒的程式碼是很少出現的,而且通常都是有害的,應當儘量避免。

四、JVM執行緒安全的實現方法

虛擬機器提供的同步和鎖機制對執行緒安全起到非常重要的作用,只要瞭解虛擬機器執行緒安全手段的運作過程,自己去思考程式碼如何編寫並不是一件困難的事情。

主要方法如下:

1 互斥同步(阻塞同步,悲觀策略)

同步是指在多個執行緒併發訪問共享資料時,保證共享資料在同一時刻只被一個或者一些執行緒使用。

互斥是實現同步的一種手段。以下是實現互斥的手段:

  • 臨界區
  • 互斥量
  • 訊號量

互斥是因,同步是果,互斥是方法,同步是目的。

互斥在程式碼中是如何體現的?

synchronized
經過編譯之後,會在同步塊的前後分別形成monitorenter和monitorexit兩個位元組碼指令,這兩個位元組碼都需要一個reference型別的引數來指明要鎖定和解鎖的物件。如果Java程式中的synchronized明確指定了物件引數,那就是這個物件的reference:如果沒有明確指定,那就根據synchronized修飾的是例項還是類方法,去取對應的物件例項或Class物件來作為鎖物件。

主要表現為以下特徵:

  • 對同一執行緒是可重入的
  • 同步塊在已進入的執行緒執行完之前,會阻塞後面其他執行緒的進入
  • synchronized是重量級操作,會執行比較長的時間,阻塞或喚醒一個執行緒,都需要作業系統來幫忙完成,這需要從使用者態轉換到核心態中。
  • 虛擬機器會對synchronized進行一些優化

ReentrantLock
與synchronized功能類似,主要表現以下特徵:

  • 等待可中斷。當持有鎖的執行緒長期不釋放鎖,正在等待的執行緒可以選擇放棄等待,改為處理其他事情
  • 公平鎖。多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖;synchronized的鎖是非公平的
  • 鎖定繫結多個條件。一個ReentrantLock物件可以同時繫結多個Condition物件,而synchronized中只能有一個條件

JDK1.6之前synchronized的效能比較差,之後的版本的效能都做了優化,所以JDK鼓勵用synchronized

2 非阻塞同步(非阻塞同步,樂觀策略)

主要是為了解決、克服執行緒阻塞和喚醒所帶來的效能問題。
這是基於衝突檢測的樂觀併發策略。
先進行操作,如果沒有其他執行緒爭用共享資料,那操作就成功了;如果共享資料有爭用產生了衝突,那就再採取其他補償措施(最常見的補償措施就是不斷地重試,直到成功為止)。

主要的體現:

CAS指令(這是一條硬體指令,因為能保證從語義行看起來需要多次操作的行為只通過一條處理器指令就能完成)

需要有三個運算元,分別是記憶體位置(在Java中可以簡單理解為變數的記憶體地址,用V表示)、舊的預期值(用A表示)和新值(用B表示)。CAS指令執行時,當且當V符合舊預期值A時,處理器用新值B更新V的值,否則它就不執行更新,但是無論是否更新了V的值,都會返回V的舊值,上述的處理過程是一個原子操作。

JDK1.5之後,JAVA程式中才可以使用CAS操作,該操作由sun.misc.Unsafe裡面的幾個方法包裝提供,虛擬機器在內部對這些方法做了特殊處理,即時編譯出來的結果就是一條平臺相關的處理器CAS指令,沒有方法呼叫過程,或者可以認為是無條件內聯進去。只有啟動類載入器載入的類才能訪問。

存在ABA問題,但是一般不會影響程式結果的正確性。

3 無同步方案

3.1 如果一個方法本來就不涉及共享資料,那它自然就無須任何同步措施去保證正確性。

3.2 可重入程式碼
在程式碼執行的任何時刻中斷它,轉而去執行另一段程式碼,在控制權返回後,原來的程式不會出現任何錯誤

  • 不依賴儲存在堆上的資料和公用的系統資源,用到的狀態量都由引數傳入、不呼叫非可重入方法等
  • 如果一個方法,它的返回結果是可以預測的,只要輸入了相同的資料,就都可能返回相同的結果

3.3 執行緒本地儲存

  • 把共享資料的可見範圍控制在同一個執行緒之內,這樣無須同步也能保證執行緒之間不出現資料爭用問題
  • 消費佇列的架構模式
  • ThreadLocal類