主要包括兩個方面的內容一個是從程序員的角度如何寫線程安全的代碼,另外一個是虛擬機底層如何實現線程安全。程序員
若是多個線程一塊兒讀寫一個共享的數據,在不加額外措施的狀況下必定會產生併發問題,這是一個老生常談的問題了。解決的方式也有不少,這裏主要從是否阻塞相關線程的角度分爲兩類。安全
一、互斥同步多線程
我更傾向於把他理解成阻塞同步。最經常使用的關鍵字是synchronized,該關鍵字編譯後會在同步的代碼塊先後增長monitorenter和monitorexit關鍵字,這兩個關鍵字都須要一個reference類型的變量來指定一個對象。從這個角度能夠理解sychronized是針對對象的。若是指明瞭sychronized是針對某一個對象的,那麼reference就會指向該對象,若是沒有的話根據sychronized指向的是類方法仍是實例方法去指向相應類對象或者實例對象。併發
該關鍵字會帶來互斥性,即任意一個時刻只能有一個線程能夠得到鎖,若是沒有成功得到那麼試圖得到線程的鎖會被阻塞知道鎖的釋放。優化
該鎖是能夠重入的,即一個線程在已經得到鎖的前提下再次嘗試得到鎖的時候是能夠得到鎖的,只不過鎖的計數器會加一。相應的一個線程在執行monitorexit以後鎖的計數器會減一,當計數器爲0的時候鎖被釋放而後喚醒別的在等待的線程。操作系統
線程A得到鎖指的是reference所指向的對象的對象頭裏指向A線程。線程
Java的線程是依賴於操做系統的線程來實現的,因此一個Java裏的線程就是操做系統裏的一個線程,JMM沒有對線程作抽象。因此線程的阻塞與喚醒須要陷入內核態(內核線程?????)這回帶來一個較大的開銷,早期的JDK會使用concurrent包中的ReentrantLock來實現同步。對象
ReentrantLock相較於sychronized有三個特色:一、等待可中斷,即一個線程在等待鎖的時候能夠選擇放棄等待。 二、能夠實現公平鎖。所謂公平鎖指的是按照申請鎖的時間的順序來得到鎖,雖然sychronized和ReentrantLock都是非公平的,可是ReentrantLock在構造的時候能夠設置成公平的 三、能夠綁定多個條件blog
二、非阻塞同步內存
阻塞同步須要把相關線程阻塞起來,阻塞和喚醒線程須要較大的代價。隨着硬件指令集的發展示在有了基於衝突檢測的樂觀併發策略。
這裏介紹了一個CAS(Compare-and-Swap)操做。CAS操做須要三個操做數:內存的地址V,舊的預期值A,新的預期值B。CAS執行的時候會比較V和A的值是否相等,只有他們相等的時候會把B操做的值賦給V操做不然不更新。這個指令看似幹了不少事情,可是經過一些硬件的實現保證這是一個原子級別的操做。
Java裏的Concurrent包裏不少都是用CAS來實現的,好比AtomicInteger。
當增長一個AtomicInteger的時候,會執行一個死循環用當前的值作A,A+1作B,而後直到CAS操做成功才推出該循環。這樣即便這個increment被打斷的時候,實時上他很大程度上會被打斷由於沒有加鎖,也不會被影響其正確性,固然雖然沒有講我猜想上面三個語句都是原子的,即curent賦值、next賦值、CAS操做是原子的。只要能保證這三個操做是原子的,那麼整個自增方法即便不是原子的,也能保證在多線程的條件下自增是線程安全的。
互斥鎖的一個很大的缺點在於線程的阻塞和喚醒須要較大的代價,一個解決思路是CAS,這裏提供了另外一種思路。當一個線程試圖得到一個鎖且失敗的時候,他不是被掛起,而是執行一個自旋即忙等待。若是持有鎖的線程能很快釋放鎖的話,那麼自旋鎖的代價是小於互斥鎖的。
爲了不一個線程長時間自旋帶來的浪費,每每設定一個閾值,當自旋超過必定次數的狀況下就再也不自旋轉而變成互斥鎖。根據如何設置閾值的不一樣能夠把鎖分爲自適應鎖和非自適應鎖,非自適應鎖的閾值是設定好的,自適應鎖如其名所言,自旋的閾值是根據上一個在該鎖上自旋的線程決定的,即若是上一個線程在這個鎖上自旋了好久,那麼個人閾值就大一些。
虛擬機分析一些鎖的必要性,若是是一個不須要加鎖的地方加了鎖那麼在編譯的時候會去處。
若是一串連續的操做都是對同一對象加鎖,那麼虛擬機會把加鎖的對象粗化對整個序列開頭和結尾加一個範圍更大的鎖。