深入理解jvm 一 執行緒安全
執行緒安全:
當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以活得正確的結果,那麼這個物件就是執行緒安全的。
java語言中得各種操作共享資料可以分成五類
1、不可變:不可變的物件一定是執行緒安全的,無論物件的方法實現還是方法的呼叫者都不需要在採取安全措施。如果共享資料是一個基本資料型別,那麼只要在定義的時候使用final關鍵字修飾就可以保證它不可變。例如java.lang.String類的物件,他就是一個典型的不可變物件,不管呼叫substring、replace、還是concat方法都不會影響它的值,只會返回一個新構造的字串物件。還有列舉類以及Number的部分子類如Long和double
2、絕對執行緒安全:絕對執行緒安全是不管執行是環境如何,呼叫者都不需要任何額外的同步措施。通常需要付出很大的甚至不切實際的代價。例如Vector是一個執行緒安全的容器,因為他的add get 和size方法都是被synchronize修飾的,但是他並不意味著呼叫它的時候永遠都不在需要同步手段了在多執行緒環境如果不做額外的措施,一個執行緒在錯誤的時間刪除一個元素,導致序號i已經不再可用,在訪問i陣列就會拋異常。
3、相對執行緒安全:就是通常意義的執行緒安全,確保這個物件單獨的操作是操作安全的。
4、執行緒相容:指的是本身物件並不是執行緒安全的,但是可以通過呼叫端的正確使用同步手段來保證物件的安全使用。
5、執行緒對立:無論呼叫端是否採取了同步措施,都無法在多執行緒環境中併發使用程式碼。
執行緒安全的實現方法
1、互斥同步:一種常見的併發同步保障手段,同步指的是在多個執行緒併發訪問共享資料時,保證共享資料在同一個時刻只能被一個執行緒使用。互斥指的是實現同步的 一種手段,臨界區互斥量和訊號量都是主要的互斥實現方式。
在java中最基本的互斥同步手段就是synchronized關鍵字,它經過編譯會形成monitorenter和monitorexit兩個位元組碼指令,他們都需要一個reference型別引數來知名要鎖定和解鎖的物件。如果是明確的物件引數就是這個物件的reference,如果沒有明確指出,那就是根據synchronized修飾的是例項方法還是類方法,去取對應的物件例項或class物件來作為鎖物件。
monitorenter執行前首先要獲取物件的鎖,如果這個物件沒有被鎖或者當前執行緒已經擁有了那個物件的鎖,就要把鎖的計數器加1
monitorenter和monitorexit會分別將鎖計時器加1和減1.一旦計數器為0時,鎖就會被釋放,如果獲取物件鎖失敗,那麼當前執行緒就會被阻塞等待,知道物件鎖被另外一個執行緒釋放為止。
synchronized同步塊對同一條執行緒來說是可重入的,不會出現自己把自己鎖死的問題,其次同步塊在已進入的執行緒執行完之前會阻塞後面其他執行緒的進入。但是synchronized是個重量級操作,只有在切實有必要的情況下才會用這種操作。
除了synchronized還可以用java.util.concurrent包中的重入鎖ReentrantLock來實現同步,它比synchronized多了一些高階功能:等待可中斷,可實現公平鎖,以及鎖可以繫結多個條件。
等待可中斷:當一個有鎖的執行緒長期不釋放鎖,其他等待的執行緒可以幹別的事情。
公平鎖:是指多個執行緒在等待同一個鎖是必須按照申請鎖的時間先後順序來依次獲得鎖。
繫結多個條件:就是new多個Condition物件。
synchronized效能不如ReentrantLock,但是在後面的jdk版本中效能已經持平了,而且還是更提倡使用更貼近原生的synchronized來進行同步。
2、非阻塞同步:
因為互斥同步的觀點是不去做正確的同步措施就會出現問題,因此是一種悲觀的併發策略。
隨著硬體指令集的發展,我們有了新的選擇,基於衝突檢測的樂觀併發策略。通俗的講就是先進行操作,如果沒有其他執行緒徵用共享資料,那操作就成功了,如果有共享資料爭用,就產生了衝突那就採取其他的補償措施,最常見的就是不斷地重試直到成功為止。
CAS指令有三個運算元,一個是記憶體位置,一個是舊的預期值,另一個是新值。當地址位置的值符合舊的預期值,就會用新值替換變數的記憶體地址,否則就不執行更新。但是無論更新與否都會返回記憶體地址的舊值,這就是一個原子操作。
但是會存在這麼一個問題,如果一個變數值是A,正在準備賦值的時候讀取到它的值是A,但是另一個執行緒突然改變了這個值變成了B,之後又改回了A,那CAS操作就會誤會這個類沒有被改變過。
3、無同步方案:
有一些程式碼天生就是執行緒安全的,主要有兩類:可重入程式碼和執行緒本地儲存。
可重入程式碼的意思一個正在執行的程式碼中斷,轉而去執行另外一段程式碼,,執行完畢後返回接著執行之前的程式碼,原來的程式不會出現任何錯誤,這就是可重入的程式碼,他可以保證執行緒安全。如果有一個方法他的返回結果是可以預測的,只要輸入了相同的資料都能返回相同的結果,那麼他就是可以滿足重入性的要求,當然也就是執行緒安全的。
執行緒本地儲存就是一段程式碼中所需要的資料必須與其他的程式碼共享,那就看看這些共享資料的程式碼能否保證在同一個執行緒中執行,如果能保證就可以把共享資料的可見範圍限制在同一個執行緒之內,這樣無需同步也能保證執行緒之間不出現資料徵用的問題。大部分使用消費佇列的架構模式都會講產品的消費過程儘量在一個執行緒內消費完。