Java多線程并發(fā)編程 并發(fā)三大要素
一、原子性
原子,一個(gè)不可再被分割的顆粒。原子性,指的是一個(gè)或多個(gè)不能再被分割的操作。
int i = 1; // 原子操作
i++; // 非原子操作,從主內(nèi)存讀取 i 到線程工作內(nèi)存,進(jìn)行 +1,再把 i 寫到朱內(nèi)存。
雖然讀取和寫入都是原子操作,但合起來(lái)就不屬于原子操作,我們又叫這種為“復(fù)合操作”。
我們可以用synchronized 或 Lock 來(lái)把這個(gè)復(fù)合操作“變成”原子操作。
例子:
private synchronized void increase(){
i++;
}
或
private int i = 0;
Lock mLock = new ReentrantLock();
private void increase() {
mLock.lock();
try {
i++;
} finally{
mLock.unlock();
}
}
這樣我們就可以把這個(gè)一個(gè)方法看做一個(gè)整體,一個(gè)不可分割的整體。
除此之前,我們還可以用java.util.concurrent.atomic里的原子變量類,可以確保所有對(duì)計(jì)數(shù)器狀態(tài)訪問(wèn)的操作都是原子的。
例子:
AtomicInteger mAtomicInteger = new AtomicInteger(0);
private void increase(){
mAtomicInteger.incrementAndGet();
}
二、可見(jiàn)性
當(dāng)多線程訪問(wèn)某一個(gè)(同一個(gè))變量時(shí),其中一條線程對(duì)此變量作出修改,其他線程可以立刻讀取到最新修改后的變量。
int i = 0;
// 線程 1 執(zhí)行
i++;
// 線程 2 執(zhí)行
System.out.print("i=" + i);
即使是在執(zhí)行完線程里的 i++ 后再執(zhí)行線程 2,線程 2 的輸入結(jié)果也會(huì)有 2 個(gè)種情況,一個(gè)是 0 和 1。
因?yàn)?i++ 在線程 1(CPU1)中做完了運(yùn)算,并沒(méi)有立刻更新到主內(nèi)存當(dāng)中,而線程 2(CPU2)就去主內(nèi)存當(dāng)中讀取并打印,此時(shí)打印的就是 0。
synchronized和Lock能夠保證可見(jiàn)性。
另外volatile關(guān)鍵字也可以解決這個(gè)問(wèn)題(下一篇會(huì)講到)。
三、有序性
我們都知道處理器為了擁有更好的運(yùn)算效率,會(huì)自動(dòng)優(yōu)化、排序執(zhí)行我們寫的代碼,但會(huì)確保執(zhí)行結(jié)果不變。
例子:
int a = 0; // 語(yǔ)句 1 int b = 0; // 語(yǔ)句 2 i++; // 語(yǔ)句 3 b++; // 語(yǔ)句 4
這一段代碼的執(zhí)行順序很有可能不是按上面的 1、2、3、4 來(lái)依次執(zhí)行,因?yàn)?1 和 2 沒(méi)有數(shù)據(jù)依賴,3 和 4 沒(méi)有數(shù)據(jù)依賴, 2、1、4、3 這樣來(lái)執(zhí)行可以嗎?完全沒(méi)問(wèn)題,處理器會(huì)自動(dòng)幫我們排序。
在單線程看來(lái)并沒(méi)有什么問(wèn)題,但在多線程則很容易出現(xiàn)問(wèn)題。
再來(lái)個(gè)例子:
// 線程 1
init();
inited = true;
// 線程 2
while(inited){
work();
}
init(); 與 inited = true; 并沒(méi)有數(shù)據(jù)的依賴,在單線程看來(lái),如果把兩句的代碼調(diào)換好像也不會(huì)出現(xiàn)問(wèn)題。
但此時(shí)處于一個(gè)多線程的環(huán)境,而處理器真的把這兩句代碼重新排序,那問(wèn)題就出現(xiàn)了,若線程 1 先執(zhí)行 inited = true; 此時(shí),init() 并沒(méi)有執(zhí)行,線程 2 就已經(jīng)開始調(diào)用 work() 方法,此時(shí)很可能造成一些奔潰或其他 BUG 的出現(xiàn)。
synchronized和Lock能確保原子性,能讓多線程執(zhí)行代碼的時(shí)候依次按順序執(zhí)行,自然就具有有序性。
而volatile關(guān)鍵字也可以解決這個(gè)問(wèn)題,volatile 關(guān)鍵字可以保證有序性,讓處理器不會(huì)把這行代碼進(jìn)行優(yōu)化排序。
相關(guān)文章
MybatisPlus出現(xiàn)Error attempting to get col
本文重點(diǎn)分析使用@EnumValue注解轉(zhuǎn)換時(shí)遇到的一下錯(cuò)誤原因,及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
詳解Maven profile配置管理及激活profile的幾種方式
這篇文章主要介紹了詳解Maven profile配置管理及激活profile的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Java手寫Redis服務(wù)端的實(shí)現(xiàn)
本文主要介紹了Java手寫Redis服務(wù)端的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
實(shí)踐講解SpringBoot自定義初始化Bean+HashMap優(yōu)化策略模式
本篇講解了SpringBoot自定義初始化Bean+HashMap優(yōu)化策略模式,通過(guò)實(shí)踐的方式更通俗易懂,對(duì)此不了解的同學(xué)跟著小編往下看吧2021-09-09
Java設(shè)計(jì)模式之責(zé)任鏈模式的示例詳解
責(zé)任鏈模式是將鏈中的每一個(gè)節(jié)點(diǎn)看做是一個(gè)對(duì)象,每個(gè)節(jié)點(diǎn)處理的請(qǐng)求均不相同,且內(nèi)部自動(dòng)維護(hù)下一個(gè)節(jié)點(diǎn)對(duì)象,當(dāng)一個(gè)請(qǐng)求從鏈?zhǔn)降氖锥伟l(fā)出時(shí),會(huì)沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn)對(duì)象。本文將通過(guò)示例和大家詳細(xì)聊聊責(zé)任鏈模式,需要的可以參考一下2022-11-11

