java 多线程不安全_多线程并发为什么不安全
一、線程安全定義
?定義:
?多個線程訪問同一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進行額外的同步,或者在調(diào)用方進行任何其他操作,調(diào)用這個對象的行為都可以獲得正確的結(jié)果,那么這個對象就是線程安全的。
該定義由Brian Goetz在《Java Concurrency In Practice》(Java并發(fā)編程實戰(zhàn))中定義;被百度百科、《深入理解Java虛擬機2》引用;
二、并發(fā)安全問題
?大概很多人都知道一點為什么在多線程并發(fā)時會不安全,多線程同時操作對象的屬性或者狀態(tài)時,會因為線程之間的信息不同步,A線程讀取到的狀態(tài)已經(jīng)過時,而A線程并不知道。所以并發(fā)安全的本質(zhì)問題在于線程之間的信息不同步!
?分析并發(fā)不安全的現(xiàn)象,再一層層展示其原理。
2.1、 競態(tài)條件
?定義:
?在并發(fā)編程中,由于不恰當(dāng)?shù)膱?zhí)行時序而出現(xiàn)不正確的結(jié)果。
?案例:
?這是一個線程不安全的方法,我們的期望是每次獲取queryTimes都會將queryTimes的值+1;但是當(dāng)多線程并發(fā)訪問時,它的工作情況并不如我們所預(yù)想的那般;
static int queryTimes = 0;
public static int getTimes(){
queryTimes = queryTimes +1;
return queryTimes;
}
?
案例圖解:
圖解說明:
當(dāng)線程A進入方法獲取到queryTimes=17時,線程B正準(zhǔn)備進入方法;
當(dāng)線程B獲取到queryTimes=18時,線程A還未處理值;
當(dāng)線程A處理queryTimes+1 = 18后,線程B隨即處理queryTimes+1 = 18;
此時線程A才將處理后到結(jié)果寫入queryTimes,隨后B也將18寫入到queryTimes;
?根據(jù)上述,我們知道當(dāng)競態(tài)條件存在時,多個線程可能同時或者幾乎同時讀取到某個狀態(tài)(值),然后將處理后到值進行寫入,此時我們可以說發(fā)生了數(shù)據(jù)的"臟讀"
?總結(jié):
?競態(tài)條件是指多線程同時對數(shù)據(jù)進行改變,讀取到臟數(shù)據(jù)或?qū)懭脲e數(shù)據(jù);
2.2、 重排序、有序性、可見性
2.2.1、 指令重排序
?定義:
?計算機為了性能優(yōu)化會對匯編指令進行重新排序,以便充分利用硬件的處理性能。
?
?案例:
int a;
int b;
int c;
...略...
a = 1; // 步驟a
b = 2; // 步驟b
c = a + b; // 步驟c
?案例圖解:
?案例分析
雖然代碼順序是步驟a、步驟b、步驟c
但是從時間上以上三種情況都有可能
原因是步驟a和步驟b并沒有依賴關(guān)系
所以為了能快點執(zhí)行,計算機會調(diào)整步驟a和步驟b的順序
因為步驟c依賴于步驟a和步驟b,所以重排序也會在a和b之后
2.2.2、 有序性
?定義:
?在Java中,單線程總是順序執(zhí)行的!
?當(dāng)編譯器和處理器重排序時,必須保證,不管怎么重排序,單線程的執(zhí)行結(jié)果不能被改變
2.2.3、 可見性
?定義:
?多線程中,若線程A中進行的每一步都可以被線程B觀測到,則稱線程A對線程B具有可見性。
?線程B不僅可以看到線程A處理的結(jié)果,還能準(zhǔn)確的知道在處理過程中,每一個狀態(tài)的改變,已經(jīng)狀態(tài)改變的順序;
?Java線程的通訊是透明的,線程之間不可以直接進行信息交換,所有的通訊必須同內(nèi)存共享!所以多線程是天然不可見的,就是說如果不主動干涉的話,線程之間不可見,為什么呢,因為線程雖然第一步處理步驟a,第二步處理步驟b,但是先將步驟b的結(jié)果寫入主內(nèi)存,后將步驟a的結(jié)果寫入主內(nèi)存,則對觀測線程來說,首先看到的是步驟b的結(jié)果,然后才是步驟a的結(jié)果!
2.3、內(nèi)存模型
?Java線程模型由主內(nèi)存和工作內(nèi)存組成;
如圖:
?說明:
工作內(nèi)存和主內(nèi)存兩部分一起組成Java線程的內(nèi)存模型
工作內(nèi)存是屬于線程的,不同線程的工作內(nèi)存之間不可共享,不可通訊
工作內(nèi)存通過Load操作從主內(nèi)存中讀取數(shù)據(jù),通過Save操作將數(shù)據(jù)寫入主內(nèi)存
線程之間的通訊:本質(zhì)上是指通過主內(nèi)存的數(shù)據(jù)共享
?解釋可見性:
?如圖,Java線程之間是不可見的,因為線程的操作都在它本身的工作內(nèi)存中完成,完成后的數(shù)據(jù)再寫入主內(nèi)存。我們稱線程之間不可見是因為線程本身沒有直接通訊機制;但是線程可以通過主內(nèi)存進行數(shù)據(jù)交換,也可以說線程之間可通過內(nèi)存通訊;
?
?解釋有序性和無序性:
?單線程有序,是因為單線程的數(shù)據(jù)操作本身在它私有的工作內(nèi)存中進行,不管如何重排序,單線程的執(zhí)行結(jié)果不可被改變,所以寫入主內(nèi)存的結(jié)果總是正確的。
a = 1; // 步驟a
b = 2; // 步驟b
c = a + b; // 步驟c
?線程在被觀測時無序,因為當(dāng)線程A中順序執(zhí)行 a = 1、b = 1時,并不能保證先將a的值寫回主內(nèi)存,完全有可能先將b的值寫入主內(nèi)存,這是不可預(yù)測的。所以在線程B中觀察線程A的處理順序,是非常不可靠的!
因為線程之間只能通過主內(nèi)存來進行數(shù)據(jù)交換,所以線程B讀到a=0,b=1時,在線程A中可能已經(jīng)時a=1,b=1。只不過還沒有及時到將a的值寫入主內(nèi)存。這樣線程B可能誤以為線程A先執(zhí)行的是b=1;
三、總結(jié)
?多線程為什么不安全?現(xiàn)在應(yīng)該有答案了!究其根本,是因為線程之間無法準(zhǔn)確的知道互相之間的狀態(tài)。那么如何使得多線程安全呢,從內(nèi)存角度來講,保證線程的工作內(nèi)存之間的可見性和有序性,是多線程并發(fā)安全的基礎(chǔ)。例如volatile關(guān)鍵字和synchronized關(guān)鍵字,我們除了從作用上了解他們,還可以從更深層的內(nèi)存語義上理解,他們之所以能夠一定程度的解決線程安全問題,是因為他們約束了一定的內(nèi)存處理方式!
總結(jié)
以上是生活随笔為你收集整理的java 多线程不安全_多线程并发为什么不安全的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java后台两个表关联查询_简单界面+J
- 下一篇: mysql 远程用户授权_mysql创建