線程安全

什麼是線程安全?咱們用《java concurrency in practice 》中的一句話來表述:當多個線程訪問一個對象時,若是不用考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方進行任何其它的協調操做,調用這個對象的行爲均可以得到正確的結果,那這個對象就是線程安全的。從這句話中咱們能夠知道幾層意思: 1.線程安全是和對象密切綁定的;2.線程的安全性是因爲線程調度和交替執行形成的;3.線程安全的目的是實現正確的結果。咱們在下面的詳細的內容中會反覆引用這個定義來講明對象的線程安全性。html

1、安全性

咱們能夠按照java共享對象的安全性,將線程安全分爲五個等級:不可變、絕對線程安全、相對線程安全、線程兼容、線程對立;java

1.1 不可變

在java中Immutable(不可變)對象必定是線程安全的,這是由於線程的調度和交替執行不會對對象形成任何改變。一樣不可變的還有自定義常量(final及常池中的對象一樣都是不可變的。api

1.2 絕對線程安全

即這些對象不管在任何環境下進行線程調度或交替執行都不會影響數據的正確性。實際上,要實現對象的絕對安全要付出的代價,它要應對各類環境和的不肯定性;甚至這種絕對性是不可能實現的,所以,在實際應用當中,javaApi 中不存在絕對線程安全的對象。數組

1.3 相對線程安全

在java api中咱們一般所說的線程安全都是相對的線程安全。以下面的示例如示安全

public class ThreadSafeTest {
    public static Vector<Integer> num =  new Vector<>();

    public static void main (String[] args) {
        while (true) {
            for (int i = 0; i < 100; i++) {
                num.add(i);
            }

            // 從後往前remove
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < num.size(); i++) {
                        num.remove(i);
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < num.size(); i++) {
                        System.out.println(num.get(i));
                    }
                }
            });

            t1.start();
            t2.start();

        }
    }
}複製代碼

咱們知道vector 是java線程安全的數組,t1 和 t2 都有可能報出數組溢出的異常。可能你們也知道緣由:在進行一次for循環的過程當中,若是發生中斷,則num.size()的大小是會變化的,當嘗試去get時,可能正好被 remove掉了。也就是說單獨去get或者是remove時,vector 是線程安全的,但組合使用卻不是線程安全的,因此vector的線程安全是有條件的,也就是相對線程安全。固然對於java 的concurrent包也是如此。bash

1.4 線程兼容

對象自己並非線程安全的,但在多線程環境中,客戶端能夠經過正確地使用同步手段來達到線程安全。咱們在java使用的大部分非線程安全的api都屬於這種,例如hashmap ,arraylist等。多線程

1.5 線程對立

線程對立指在多併發環境中,不管採起任何同步措施都不該該同時出現的代碼。好比Thread suspend(中斷)和resume(恢復),若是前一個線程進行了中斷,另一個線程嘗試resume時,則獲取不到該對象,則會產生死鎖。(www.zhihu.com/question/40…架構

2、同步

影響對象線程安全是在多併發狀況下,多個線程之間的調度和交替執行,影響對象的狀態,那如何實現多線程之間的數據同步將是解決線程安全的關鍵所在。併發

2.1 互斥同步

互斥同步即當一個線程在對對象進行操做時,其它線程沒法對該對象作任何操做。實現互斥同步有多種方法,如臨界區、互斥量、信號量都是實現互斥同步的方式。
java實現同步的主要手段是使用synchronized關鍵字,它通過編譯以後,會在同步塊先後分別造成monitorenter 和 monitorexit 兩個字 節碼,這兩個字節碼都須要一個reference類型的對參數,來講明須要加鎖和解鎖的對象。如synchronized 沒有指定鎖定的對象,則指向該類對應的對象或者類自己(若是是靜態類)。當執行monitorenter時,鎖計數器就會加1,相反當 執行monitorexit時,計數器減1,當計數器爲0時,就會釋放該對象。固然, synchronized 對同一條線程是可重入的,以免自已被本身鎖死的狀況。
除synchronized 外,java.util.concurrent 包還提供了重入鎖(ReentrantLock)來實現同步, 重入鎖提供了更加豐富的功能:
等待可中斷:指當持有鎖的線程長期不釋放鎖時,則放棄等待轉而作其它事情
公平鎖 :能夠按照時間順序獲取鎖(但默認實現仍然是非公平鎖)
鎖綁定多個條件ide

對synchronized 和ReentrantLock 的更詳細的對比參考:blog.csdn.net/fw0124/arti…
互斥同步屬於較重的同步,它的性能也相對比較低。由於,對兩個線程對同一個對象的訪問,不管是否會存在數據同步的問題,都要進行鎖競爭,它屬於一種悲觀鎖。線程等待鎖釋放的阻塞過程嚴重下降了代碼的運行效率。

2.2 非阻塞同步

爲了解決互斥同步的線程阻塞問題,產生了非阻塞同步。非阻塞同步屬於樂觀鎖:基於衝突檢測的樂觀併發策略,通谷地說,它是先進行操做,若是沒有其它線程爭用,則操做成功;若是有其它線程爭用,產生了衝突,則進行衝突補償。樂觀鎖的出現是在硬件發展的基礎之上產生的,爲何這麼說呢,這是由於須要保證咱們對數據的操做和衝突檢測具備原子性,這須要硬件的單一指令來實現。最多見的指令是(compare-and-swap)(詳細介紹見:www.cnblogs.com/Mainz/p/354…
非阻塞同步並非要替代互斥同步,只是互斥同步的一個補充,緣由在於它的高性能。但因爲其在設計上的缺陷(ABA問題),並不能成爲互斥同步的一個替代品。

2.3 無同步

並非全部的數據都須要同步,若是不存在數據爭用就不須要進行同步。
另外,針對部分情境(共享數據的代碼在一條線程內執行)也可使用線程本地存儲(Thread Local storage),它特別適用於消費隊列的架構模式。

3、鎖

大部分的數據同步的基礎仍然是互斥同步,咱們從上面能夠知道互斥同步在進行鎖競爭時,線程會被阻塞,並在掛起和喚醒中不斷切換,這種線程切換對性能的影響是比較大的。因爲以上緣由,鎖優化就成了JAVA的重要工做,下面我就來看一下有哪些常見的鎖優化。

3.1 自旋鎖與自適應自旋

在大部分狀況下,當一個線程未競爭到鎖時,它一般只須要等待很短的時候,就能從新得到鎖,因此java 就採用循環忙等待的方式來等待鎖的釋放,而不是掛起和恢復線程。可是忙等待也不是無損失的,它須要佔用cpu資源,若是等待了很長時間,鎖也得不到釋放,也是很可怕的資源浪費。
因此在1.4時,能夠經過參數控制自旋次數,超過次數線程就會被掛起。在1.6時,java則採用了自適應的方式來控制自旋次數,而不須要經過人工進行設置。

3.2 鎖消除

鎖消除是指對於JVM 即時編譯時,具備代碼同步要求,但實際上共享數據並不存在競爭狀況的鎖進行消除。共享數據是否存在競爭,依賴JVM的逃逸分析技術(www.importnew.com/23150.html)

3.3 鎖粗化

雖然不少時候咱們都但願對鎖的粒度進行細化,以減小鎖競爭的代碼範圍;但當,一個代碼塊須要用多個鎖進行同步時,則能夠考慮使用一個鎖對整個代碼塊進行鎖定,以減小鎖的競爭開銷。

3.4 輕量級鎖

輕量級鎖是相對於重量級鎖而言的,它假設數據不存在競爭的狀況下,來提高鎖的性能。提高鎖性能的方式,就是摒棄重量級鎖利用系統互斥量來對對象加鎖,而是採用對象頭部的標誌位和cas操做來進行鎖競爭檢測。具體的實現原理參考:
my.oschina.net/u/140462/bl…

3.5 偏向鎖

偏向鎖相似於輕量級鎖 是對無競爭狀況下的優化。因爲輕量級鎖仍然須要對markword進行同步,而偏向鎖則是消除這種同步,進一步地優化性能。偏向鎖經過在markword中寫入threadId和標誌位,來對對象鎖定,若是在對對象的操做過程當中,其markword部分未被更改,則表示不存在競爭,也就無需同步。若是被更改,則說明存在競爭,會上升到輕量級鎖或重量級鎖進行同步操做。偏向鎖的實現原理參考:my.oschina.net/u/140462/bl…

相關文章
相關標籤/搜索