定義:java
多個線程訪問同一個對象時,若是不用考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方進行任何其餘操做,調用這個對象的行爲均可以得到正確的結果,那麼這個對象就是線程安全的。編程
該定義由Brian Goetz在《Java Concurrency In Practice》(Java併發編程實戰)中定義;被百度百科、《深刻理解Java虛擬機2》引用;安全
大概不少人都知道一點爲何在多線程併發時會不安全,多線程同時操做對象的屬性或者狀態時,會由於線程之間的信息不一樣步,A線程讀取到的狀態已通過時,而A線程並不知道。因此併發安全的本質問題在於線程之間的信息不一樣步!性能優化
分析併發不安全的現象,再一層層展現其原理。多線程
定義:併發
在併發編程中,因爲不恰當的執行時序而出現不正確的結果。性能
案例:優化
這是一個線程不安全的方法,咱們的指望是每次獲取queryTimes都會將queryTimes的值+1;可是當多線程併發訪問時,它的工做狀況並不如咱們所預想的那般;線程
static int queryTimes = 0; public static int getTimes(){ queryTimes = queryTimes +1; return queryTimes; }
案例圖解:
圖解說明:
當線程A進入方法獲取到queryTimes=17時,線程B正準備進入方法;
當線程B獲取到queryTimes=18時,線程A還未處理值;
當線程A處理queryTimes+1 = 18後,線程B隨即處理queryTimes+1 = 18;
此時線程A纔將處理後到結果寫入queryTimes,隨後B也將18寫入到queryTimes;
根據上述,咱們知道當競態條件存在時,多個線程可能同時或者幾乎同時讀取到某個狀態(值),而後將處理後到值進行寫入,此時咱們能夠說發生了數據的"髒讀"
總結:
競態條件是指多線程同時對數據進行改變,讀取到髒數據或寫入錯數據;
2.2.一、 指令重排序
定義:
計算機爲了性能優化會對彙編指令進行從新排序,以便充分利用硬件的處理性能。
案例:
int a; int b; int c; ...略... a = 1; // 步驟a b = 2; // 步驟b c = a + b; // 步驟c
案例圖解:
案例分析
2.2.二、 有序性
定義:
在Java中,單線程老是順序執行的!
當編譯器和處理器重排序時,必須保證,無論怎麼重排序,單線程的執行結果不能被改變
2.2.三、 可見性
定義:
多線程中,若線程A中進行的每一步均可以被線程B觀測到,則稱線程A對線程B具備可見性。
線程B不只能夠看到線程A處理的結果,還能準確的知道在處理過程當中,每個狀態的改變,已經狀態改變的順序;
Java線程的通信是透明的,線程之間不能夠直接進行信息交換,全部的通信必須同內存共享!因此多線程是自然不可見的,就是說若是不主動干涉的話,線程之間不可見,爲何呢,由於線程雖然第一步處理步驟a,第二步處理步驟b,可是先將步驟b的結果寫入主內存,後將步驟a的結果寫入主內存,則對觀測線程來講,首先看到的是步驟b的結果,而後纔是步驟a的結果!
Java線程模型由主內存和工做內存組成;
如圖:
說明:
解釋可見性:
如圖,Java線程之間是不可見的,由於線程的操做都在它自己的工做內存中完成,完成後的數據再寫入主內存。咱們稱線程之間不可見是由於線程自己沒有直接通信機制;可是線程能夠經過主內存進行數據交換,也能夠說線程之間可經過內存通信;
解釋有序性和無序性:
單線程有序,是由於單線程的數據操做自己在它私有的工做內存中進行,無論如何重排序,單線程的執行結果不可被改變,因此寫入主內存的結果老是正確的。
a = 1; // 步驟a b = 2; // 步驟b c = a + b; // 步驟c
線程在被觀測時無序,由於當線程A中順序執行 a = 一、b = 1時,並不能保證先將a的值寫回主內存,徹底有可能先將b的值寫入主內存,這是不可預測的。因此在線程B中觀察線程A的處理順序,是很是不可靠的!
由於線程之間只能經過主內存來進行數據交換,因此線程B讀到a=0,b=1時,在線程A中可能已經時a=1,b=1。只不過尚未及時到將a的值寫入主內存。這樣線程B可能誤覺得線程A先執行的是b=1;
多線程爲何不安全?如今應該有答案了!究其根本,是由於線程之間沒法準確的知道互相之間的狀態。那麼如何使得多線程安全呢,從內存角度來說,保證線程的工做內存之間的可見性和有序性,是多線程併發安全的基礎。例如volatile關鍵字和synchronized關鍵字,咱們除了從做用上了解他們,還能夠從更深層的內存語義上理解,他們之因此可以必定程度的解決線程安全問題,是由於他們約束了必定的內存處理方式!