目錄
線程安全的理解
線程不安全的原因
①非原子性
②可見性
③代碼重排序
體會何為不安全的線程?
保證線程安全
一個(gè)代碼在多線程的環(huán)境下就很容易出現(xiàn)錯(cuò)誤.
線程安全的理解
? 線程安全是什么呢?通俗的來講,線程安全就是在多線程的環(huán)境下,代碼的結(jié)果是符合我們預(yù)期的,就可以稱這個(gè)線程是安全的.
線程不安全的原因
①非原子性
關(guān)于原子性:
? 原子是最小的粒子,不可被分割.所以,原子性就是代表一系列不能被分割的操作.
? 可以這樣說,在一節(jié)開往學(xué)校的火車的10號車廂上.你因?yàn)楹攘颂嗨?突然想上廁所.于是便走到10號車險(xiǎn)的公共廁所上,開展了"觀察廁所是否有人","打開廁所門","進(jìn)入廁所","關(guān)閉廁所門","上鎖","上廁所","解鎖","打開廁所門","走出廁所","關(guān)閉廁所門"著這一系列的操作.
?我們應(yīng)該很容易理解這一系列操作有著原子性,既是是連續(xù)且不可被分割的吧.總不能在你進(jìn)入廁所關(guān)上門后允許有人打開你的廁所門.
?而上鎖這一操作就很好的保持了這一系列的原子性,因?yàn)楫?dāng)你在廁所執(zhí)行"任務(wù)"的時(shí)候,有人想要打開你的廁所門也進(jìn)行不了這一操作.因?yàn)榇藭r(shí)門被上鎖了,是不可能被打開的.除非你在里面進(jìn)行了解鎖這一操作.
在java當(dāng)中,原子性是一條語句嗎?并不是的:
int n = 0;
n++;
像是上面的代碼的"n++"語句,是不是就很容易被理解具有原子性.
java是一門高級編程語言,在其之下的稱之為匯編語言.在java中我們只是看到簡單的n++一條語句,而在匯編語言中他的代碼大致邏輯是:
- 從內(nèi)存把數(shù)據(jù)讀取到cpu中
- 進(jìn)行數(shù)據(jù)的更新
- 將新數(shù)據(jù)寫回cpu中
因?yàn)檫M(jìn)程里的線程的調(diào)度是具有隨機(jī)性的,在你執(zhí)行把數(shù)據(jù)更新完但還沒有寫回這一步的時(shí)候,調(diào)度到了另一個(gè)線程,而且新被調(diào)度到的線程也要使用這個(gè)變量.這時(shí)候就會發(fā)生不對等的情況.
②可見性
? 在我們線程的基礎(chǔ)知識中提及到,同一個(gè)進(jìn)程中的多個(gè)線程之間是具有關(guān)聯(lián)性的,線程間也共享著同一個(gè)空間.在這一基礎(chǔ)上:可見性指, 一個(gè)線程對共享變量值的修改,能夠及時(shí)地被其他線程看到.
JMM內(nèi)存模型:
?JMM模型是一個(gè)抽象的邏輯型,規(guī)定了一個(gè)進(jìn)程中的所有變量都要存儲在主內(nèi)存中,進(jìn)程中的線程又是共享同一個(gè)空間,所以說同一個(gè)線程中的所有線程都可以訪問到主內(nèi)存.同時(shí),進(jìn)程中的線程又有一個(gè)單獨(dú)的工作內(nèi)存.
- 當(dāng)線程要讀取一個(gè)主內(nèi)存中的共享變量時(shí),先要把主內(nèi)存中的共享變量拷貝到工作內(nèi)存中,再從工作內(nèi)存中讀取此數(shù)據(jù)
- 當(dāng)線程要更新一個(gè)主內(nèi)存中的共享變量時(shí),先要把主內(nèi)存中的共享變量拷貝到工作內(nèi)存中并進(jìn)行更新拷貝過來的副本,再去更改主內(nèi)存的共享變量?
?因?yàn)檫@個(gè)可見性的緣故,線程1要修改共享變量的時(shí)候,線程2的同一個(gè)變量的數(shù)據(jù)還沒有得到更新.就會導(dǎo)致結(jié)果的偏差.
③代碼重排序
JVM、CPU指令集會對代碼進(jìn)行優(yōu)化
編譯器對于指令重排序的前提是 "保持邏輯不發(fā)生變化". 這一點(diǎn)在單線程環(huán)境下比較容易判斷, 但
是在多線程環(huán)境下就沒那么容易了, 多線程的代碼執(zhí)行復(fù)雜程度更高, 編譯器很難在編譯階段對代
碼的執(zhí)行效果進(jìn)行預(yù)測, 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià)
體會何為不安全的線程?
public class Test {
public static int n = 0;
public static void count(){
n++;
}
public static void main(String[] args) throws InterruptedException {
//線程0
Thread thread0 = new Thread(() ->{
for(int i = 0; i < 10000; i++){
count();//每次調(diào)用,n+1.預(yù)取:線程0能使n加上10000次
}
});
//線程1
Thread thread1 = new Thread(() ->{
for(int i = 0; i < 10000; i++){
count();//每次調(diào)用,n+1.預(yù)取:線程1能使n加上10000次
}
});
thread0.start();//啟動
thread1.start();
//等到線程0,線程1執(zhí)行完成才去打印
thread0.join();
thread1.join();
//我們的預(yù)期是,線程0加上10000,線程1加上10000.n為20000
System.out.println(n);
}
}
出現(xiàn)的結(jié)果不符合我們期望,就是因?yàn)閏ount方法中沒有保持原子性,導(dǎo)致兩個(gè)線程間同一數(shù)據(jù)讀出和寫出的步驟重合而最終的數(shù)據(jù)錯(cuò)誤.
通俗的來說,在多線程環(huán)境下可能出現(xiàn)線程不安全的原因有:
- 線程是搶占式執(zhí)行的,隨機(jī)調(diào)度導(dǎo)致的
- 多個(gè)線程修改同一個(gè)變量,會出現(xiàn)可見性的問題
- 線程中的修改操作不是原子性的?
保證線程安全
? ?可以給一個(gè)代碼塊使用synchronized關(guān)鍵字進(jìn)行修飾,達(dá)到了上鎖的作用.保證其原子性
? ?我們更新一下,在count方法上使用synchronized修飾.在每一個(gè)線程調(diào)用的時(shí)候,其他線程對于這個(gè)count方法都會變?yōu)樽枞麪顟B(tài),即不能調(diào)用這個(gè)方法.
public static synchronized void count(){
n++;
}
?最后的結(jié)果,我們就可以很好的保證了預(yù)期結(jié)果的正確,達(dá)到了多線程環(huán)境下的安全.文章來源:http://www.zghlxwxcb.cn/news/detail-600900.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-600900.html
到了這里,關(guān)于[JAVAee]線程安全的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!