本篇文章介紹一些多線程的相關的深刻概念。理解後對於線程的安全性會有更深的理解。html
先說一個格言,摘自Java核心技術:
若是向一個變量寫入值,而這個變量接下來可能會被另外一個線程讀取;或者一個變量讀值,而這個變量多是以前被另外一個線程寫入的,此時必須同步。java
下面就是概念了。程序員
Monitor實際上是一種同步工具、同步機制,一般被描述成一個對象,主要特色是:編程
互斥
的執行。比如一個Monitor只有一個運行「許可」,任一個線程進入任何一個方法都須要得到這個「許可」,離開時把許可歸還。在 Monitor Object 模式中,主要有四種類型參與者:數組
Java中,Object 類自己就是監視者對象,Java 對於 Monitor Object 模式作了內建的支持。緩存
Java的併發採用的是共享內存模型,線程間通訊是隱式的,同步是顯示的;而咱們在Android中所常說的Handler通訊即採用的是消息傳遞模型,通訊是顯示的,同步是隱式的。安全
併發編程模型的分類
併發編程中,須要處理兩個問題:線程之間如何通訊、線程之間如何同步。bash
Java內存模型的抽象
Java堆內存在線程間共享,下文所說的共享變量即被存儲在堆內存中變量:實例域、靜態域和數組。局部變量、方法定義參數和異常處理參數不會在線程之間共享,不會有內存可見性問題,也不受內存模型影響。多線程
Java線程之間的通訊由Java內存模型(JMM,Java Memory Module)控制,JMM決定了一個線程對共享變量的寫入什麼時候對另外一個線程可見。
JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存中,每一個線程都有一個私有的本地內存,也叫工做內存,本地內存中存儲了該線程以讀/寫共享變量的副本。(本地內存是JMM的一個抽象概念,並不真實存在,它涵蓋了緩存、寫緩衝區、寄存器以及其餘的硬件和編譯器優化。)
因此線程A和線程B要通訊步驟以下:併發
線程模型圖
原子性指:一個操做(有可能包含有多個子操做)要麼所有執行(生效),要麼所有都不執行(都不生效)。
java.util.concurrent.atomic包中不少類使用了CAS指令來保證原子性,而再也不使用鎖。如AtomicInterger
、AtomicBoolean
、AtomicLong
、AtomicReference
等。
原子性不保證順序一致性,只保證操做是原子的。
可見性是指,當多個線程併發訪問共享變量時,一個線程對共享變量的修改,其它線程可以當即看到。
happens-before規則對應於一個或多個編譯器和處理器重排序規則,對於程序員來講,該規則易懂,避免爲了理解JMM提供的內存可見性保證而去學習複雜的重排序規則以及這些規則的具體實現。
使用happens-before的概念來闡述操做之間的內存可見性。
若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在happens-before關係。
這兩個操做能夠在一個線程內,也能夠是不一樣線程。
兩個操做之間具備happens-before關係,並不意味着前一個操做必需要在後一個操做前執行;僅僅要求前一個操做的執行結果對後一個可見,且前一個操做按順序排在第二個操做以前。
是現代處理器上提供的高效機器級別的原子指令,這些原子指令以原子方式對內存執行讀-寫-改操做,這是在多處理器中實現同步的關鍵。AtomicInterger
、AtomicBoolean
、AtomicLong
的實現都是基於CAS指令。
在計算機中,軟件技術和硬件技術有一個共同的目標:在不改變程序執行結果的前提下,儘量的提升開發並行度。
as-if-serial語義
是指無論怎麼重排序,單線程程序的執行結果不能被改變。編譯器、runtime和處理器都必須遵照as-if-serial語義。
因此,編譯器和處理器不會對存在數據依賴關係的操做作重排序,由於這種重排序會改變執行結果。
數據依賴性
重排序對多線程的影響
重排序破壞了多線程程序的語義。對於存在控制依賴的操做(if語句)進行重排序,由於單線程程序是按順序來執行的,因此執行結果不會改變;而多線程程序中,重排序可能會改變運行結果。
對控制依賴if(flag){b = a*a}
的重排序以下,編譯器和處理器會採用猜想執行來克服相關性來對並行度的影響,對先提取並計算a*a,而後把計算結果保存到名爲重排序緩衝的硬件緩存中,接下來再判斷flag是否爲真。另外一個線程設置爲true了,並設置a=1,然而取得的值可能爲0,與預期不符。這就是影響的一個案例。
重排序的一個示例,摘自EffectiveJava:
while(!done) {
i++
}
//重排後。這種優化稱做提示,是HopSpot Server VM的工做
if(!done){
while(true) {
i++;
}
}複製代碼
若是一個多線程程序能正確同步,這個程序將是一個沒有數據競爭的程序。JMM對正確同步的多線程程序的內存一致性作了以下保證:若是程序是正確同步的,程序的執行將具備順序一致性,即程序的執行結果與該程序在順序一致性內存模型中的執行結果相同。
首先要明確,線程的安全性須要三點保證:原子性、可見性,順序性。只有知足了這三個條件時線程纔是安全的。
synchronized、Lock徹底保證了這三點;volatile僅保證了可見性和順序性(禁止指令重排),在某些狀況下可使用volatile代替synchronized以提升性能。在這種狀況下,volatile是輕量級的synchronized。
某些狀況下是指:
假設對共享變量除了賦值之外並不完成其餘操做,那麼能夠將這些共享變量聲明爲volatile。即共享變量自己的操做是原子性的、順序性的,只缺可見性了,此時能夠用volatile關鍵字。在使用時要仔細分析。
具體是指:
要記住,原子性指的是對共享變量的操做(包括其子操做,即多條語句)是一塊的,要麼執行,要麼不執行。不是說用了AtomicInteger就是原子性的,而是對AtomicInteger這個共享變量的操做是否是多條語句,這些多條語句是否是原子性的。
經典示例1:單例模式
經典示例2:
boolean volatile isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;//只有賦值操做,非多條語句
}複製代碼
參考:
Java進階(二)當咱們說線程安全時,到底在說什麼
Java併發編程:volatile關鍵字解析
併發模型——共享內存模型(線程與鎖)理論篇
《深刻理解Java內存模型》