一.到底什麼叫線程安全:
java併發編程實戰中對線程安全的定義是:當多個線程訪問一個對象時,若是不用考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方進行任何其餘的協調操做,調用這個對象的行爲均可以得到正確的結果,那這個對象是線程安全的」。這個定義比較嚴格,通常咱們都會將其弱化。
按照線程安全的「安全程度」由強至弱來排序,咱們能夠將Java語言中各類操做共享的數據分爲如下5類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立。
1.不可變:如String
2.絕對線程安全就是知足java併發編程實戰中對線程安全的定義
在javaAPI中標註本身是線程安全的類,大都不是絕對的線程安全。如Vector,它的add,get,size方法都加了synchronized修飾的,但請看下面一段代碼:java
private static Vector<Integer> vector = new Vector<Integer>(); public static void main(String[] args) { while (true) { for (int i = 0; i < 10; i++) { vector.add(i); } Thread removeThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < vector.size(); i++) { vector.remove(i); } } }); Thread printThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < vector.size(); i++) { System.out.println((vector.get(i))); } } }); removeThread.start(); printThread.start(); //不要同時產生過多的線程,不然會致使操做系統假死 while (Thread.activeCount() > 20); } }
這段代碼有可能產生問題,由於若是在printThread恰好要打印最後一個元素時removeThread剛好刪除了一個元素,則printThread就會產生ArrayIndexOutOfBoundsException編程
3.相對線程安全
相對的線程安全就是咱們一般意義上所講的線程安全,它須要保證對這個對象單獨的操做是線程安全的,咱們在調用的時候不須要作額外的保障措施,可是對於一些特定順序的連續調用,就可能須要在調用端使用額外的同步手段來保證調用的正確性。如上面的Vector容器就是相對線程安全安全
4.線程兼容
線程兼容是指對象自己並非線程安全的,可是能夠經過在調用端正確地使用同步手段來保證對象在併發環境中能夠安全地使用,咱們日常說一個類不是線程安全的,絕大多數時候指的是這一種狀況多線程
5.線程對立
指的是指不管調用端是否採起了同步措施,都沒法在多線程環境中併發使用的代碼。如Thread的suspend()和resume()併發
二.實現線程安全的方法
1.互斥同步
互斥是因,同步是果;互斥是方法,同步是目的。
在Java中,最基本的互斥同步手段就是synchronized關鍵字,synchronized關鍵字通過編譯以後,會在同步塊的先後分別造成monitorenter和monitorexit這兩個字節碼指令,這兩個字節碼都須要一個reference類型的參數來指明要鎖定和解鎖的對象。
synchronized同步塊對同一條線程來講是可重入的,不會出現本身把本身鎖死的問題。ide
2.非阻塞同步
互斥同步也叫阻塞同步。阻塞同步屬於一種悲觀的併發策略,在有可能出現併發問題的地方都要進行同步措施(如加鎖)。非阻塞同步採用樂觀的併發策略,經過沖突檢測檢查是否有競爭,有的進行補償措施(如不斷重試直到成功),由於他不用把線程掛起因此是非阻塞的。但是這就行了嗎,要知道咱們確定要保證衝突檢測和操做這兩個操做的原子性啊,那怎麼保證呢,確定不能用互斥同步(這樣的話仍是變成會阻塞的),答案是必需要有硬件指令集的支持來保證原子性。
這類經常使用指令有:
測試並設置(Test-and-Set)。
獲取並增長(Fetch-and-Increment)。
交換(Swap)。
比較並交換(Compare-and-Swap,下文稱CAS)。
加載連接/條件存儲(Load-Linked/Store-Conditional,下文稱LL/SC)。測試
Java中的CAS操做由sun.misc.Unsafe類裏面的compareAndSwapInt()和compareAndSwapLong()等幾個方法包裝提供,虛擬機在內部對這些方法作了特殊處理,即時編譯出來的結果就是一條平臺相關的處理器CAS指令,沒有方法調用的過程,或者能夠認爲是無條件內聯進去了。
因爲Unsafe類不是提供給用戶程序調用的類(Unsafe.getUnsafe()的代碼中限制了只有啓動類加載器(Bootstrap ClassLoader)加載的Class才能訪問它),所以,若是不採用反射手段,咱們只能經過其餘的JavaAPI來間接使用它,如J.U.C包裏面的整數原子類,其中的compareAndSet()和getAndIncrement()等方法都使用了Unsafe類的CAS操做。優化
三.鎖優化
1.自旋鎖與自適應自旋
自旋就是獲取鎖失敗的時候讓線程忙循環,而不是阻塞,這能夠提升響應速度以及避免線程切換的開銷,但浪費CPU時間。
自適應自旋就是若是循環獲取必定次數還沒獲取成功,就阻塞。
自旋鎖在JDK中默認關閉,可以使用用-XX:+UseSpinning開啓spa
2.鎖消除
鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除。鎖消除的主要斷定依據來源於逃逸分析的數據支持,若是判斷在一段代碼中,堆上的全部數據都不會逃逸出去從章已經講解過逃逸分析技術),若是判斷在一段代碼中,堆上的全部數據都不會逃逸出去從而被其餘線程訪問到,那就能夠把它們當作棧上數據對待,認爲它們是線程私有的,同步加鎖天然就無須進行。
3.鎖粗化
防止過於頻繁的加鎖解鎖操做操作系統
4.輕量級鎖
在代碼進入同步塊的時候,若是此同步對象沒有被鎖定(鎖標誌位爲「01」狀態),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝(官方叫Displaced Mark Word)。
5.偏向鎖偏向鎖優化點加解鎖在於連CAS操做都不用了