當多個線程訪問一個對象時,若是不用考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方進行任何其餘的協調操做,調用這個對象的行爲均可以得到正確的結果,那這個對象是線程安全的。java
按線程安全的「安全程度」由強至弱排序,能夠將多個線程的共享數據分爲 5 類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立。程序員
不可變的對象必定是線程安全的,不管是對象的方法實現仍是方法的調用者,都不須要再採起任何的線程安全保障措施。安全
必須知足「無論運行時環境如何,調用者都不須要任何額外的同步措施」。多線程
實現代價很是大。併發
就是咱們一般意義上所講的線程安全,它須要保證對一個對象單獨的操做是線程安全的,調用時不須要額外的保障措施,可是對於一些特定順序的連續調用,可能須要在調用端使用額外的同步手段來保證調用的正確性。函數
Java 語言中,大部分的線程安全類都屬於這種類型。性能
指對象自己並非線程安全的,但能夠經過在調用端正確地使用同步手段來保證對象在併發環境中能夠安全地使用。優化
咱們日常說一個類不是線程安全的,絕大多數時候指的是這一種狀況。操作系統
指不管調用端是否採起了同步措施,都沒法在多線程環境中併發使用的代碼。線程
Java 語言天生就具有多線程特性,線程對立這種排斥多線程的代碼是不多出現的。
同步是指在多個線程併發訪問共享數據時,保證共享數據在同一個時刻只被一個(使用信號量時能夠是多個)線程使用。而互斥是實現同步的一種手段,臨界區、互斥量和信號量都是主要的互斥實現方式。
從處理問題的方式上說,互斥同步屬於一種悲觀的併發策略,老是認爲只要不去作正確的同步措施(例如加鎖),那就確定會出現問題。
synchronized:
synchronized 關鍵字是 Java 語言最基本的互斥同步手段,通過編譯後,它會在同步塊的先後分別造成 monitorenter 和 monitorexit 兩個字節碼指令,這兩個字節碼都須要一個 reference 類型的參數來指明要鎖定和解鎖的對象。
ReentrantLock(重入鎖):
java.util.concurrent 包中的 ReentrantLock 也能夠用來實現同步,但相比 synchronized,ReentrantLock 增長了一些高級功能:
非阻塞同步是一種樂觀的併發策略,它在進行同步操做時,不須要把線程掛起,而是先進行操做,若是沒有其餘線程爭用共享數據,那操做就成功了;若是共享數據有爭用,產生了衝突,那就再採起其餘的補償措施(最多見的補償措施就是不斷地重試,直到成功爲止)。
CAS(比較並交換):
CAS 指令有 3 個操做數:變量的內存地址 V、舊的預期值 A、新值 B。當且僅當內存地址 V 的值與預期值 A 相等時,將內存地址 V 的值更新爲新值 B,不然不執行更新。上述的處理過程是一個原子操做。
CAS 的 ABA 問題:
若是一個變量 V 初次讀取的時候是 A 值,而且在準備賦值的時候檢查到它仍然爲 A 值,CAS 操做會認爲它歷來沒有被改變過。但實際上,在這段期間它的值有可能曾經被改爲了 B,後來又被改回爲 A。這個漏洞稱爲 CAS 操做的「ABA」問題。
若是一個方法原本就不涉及共享數據,那它天然就無須任何同步措施去保證正確性,所以會有一些代碼天生就是線程安全的。
可重入代碼(純代碼):
若是一個方法,它的返回結果是能夠預測的,只要輸入了相同的數據,就都能返回相同的結果,那它就知足可重入性的要求,固然也就是線程安全的。
可重入代碼有一些共同的特徵,例如不依賴存儲在堆上的數據和公用的系統資源、用到的狀態量都由參數中傳入、不調用非可重入的方法等。
線程本地存儲:
若是能保證使用共享數據的代碼在同一個線程中執行,那麼就能夠把共享數據的可見範圍限制在同一個線程以內,這樣,無須同步也能保證線程之間不出現數據爭用的問題。
若是物理機有一個以上的處理器,能讓兩個或以上的線程同時並行執行,咱們就可讓後面請求鎖的那個線程「稍等一下」,但不放棄處理器的執行時間,而後看看持有鎖的線程是否很快就會釋放鎖。爲了讓線程等待,咱們只需讓線程執行一個忙循環(自旋),這就是所謂的自旋鎖。
自適應自旋:
經過前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來肯定自旋的時間。若是在同一個鎖對象上,自旋等待剛剛成功得到過鎖,那麼虛擬機將容許自旋等待持續相對更長的時間。若是對於某個鎖,自旋不多成功得到過,那在之後要獲取這個鎖時將可能省略掉自旋過程,以免浪費處理器資源。
鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除。
鎖消除的主要斷定依據來源於逃逸分析的數據支持,若是判斷在一段代碼中,堆上的全部數據都不會逃逸出去從而被其餘線程訪問到,那就能夠把它們當作棧上數據對待,認爲它們是線程私有的,同步加鎖天然就無須進行。
若是一系列的連續操做都對同一個對象反覆加鎖和解鎖,甚至加鎖操做是出如今循環體中的,那麼虛擬機將會把加鎖同步的範圍擴展(粗化)到整個操做序列的外部,這樣只須要加鎖一次就能夠了。
輕量級鎖並非用來代替重量級鎖的,而是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。
對象頭的 Mark Word 實現輕量級鎖和偏向鎖的關鍵。Mark Word 有一個 2bit 的鎖標誌位,用於標識對象的鎖狀態。
輕量級鎖的加鎖過程:
輕量級鎖的解鎖過程:
若是說輕量級鎖是在無競爭的狀況下使用 CAS 操做去消除同步使用的互斥量,那偏向鎖就是在無競爭的狀況下把整個同步都消除掉,連 CAS 操做都不作了。
偏向鎖是指這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中,該鎖沒有被其餘線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。
偏向鎖鎖的執行過程:
媽呀!終於寫完了~
講道理,當初花三個月把這本書啃完的時候,原本還打算至少在過年的時侯把筆記整理完的。沒成想,只由於看了一眼《半小時漫畫中國史》,頓時就以爲硬邦邦的技術書籍一點也不香了。直到我看完了三本《半小時漫畫中國史》才恍然驚醒——
唉?我只是個假的文藝青年啊,看這種書不太好吧?身爲假程序員的我,真是羞愧!
後來呢,由於疫情的緣由,過年的假期延長了一週。那段時間我就一直在想:我是否是忘記了什麼?然而,由於我告訴我:作任何事情都要專一,開黑的時候不能分心。我以爲我說得挺有道理的,因此我也就沒太深究。後來我才知道,原來我得了失憶症,這種失憶症的名字叫作「過後自我欺騙型失憶症」。
唉,真是羞愧!