Java中多線程與并發(fā)_volatile關(guān)鍵字的深入理解
一、volatile關(guān)鍵字
volatile是JVM提供的一種輕量級的同步機制,特性:
1.保證內(nèi)存可見性
2.不保證原子性
3.防止指令重排序
二、JMM(Java Memory Model)
Java內(nèi)存模型中規(guī)定了所有的變量都存儲在主內(nèi)存中(如虛擬機物理內(nèi)存中的一部分),每條線程還有自己的工作內(nèi)存(如CPU中的高速緩存),線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存的副本拷貝,線程對變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量。不同線程之間無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成,線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示:

三、驗證
1.驗證volatile的可見性
1.1 假如 int num = 0; num變量之前根本沒有添加volatile關(guān)鍵字修飾,沒有可見性
1.2 添加了volatile,可以解決可見性問題
MyData類
class MyData {
volatile int num = 0;
public void addT060() {
this.num = 60;
}
}
內(nèi)存可見性驗證,其中兩個線程分別為AAA線程和main線程
//volatile可以保證可見性,及時通知其它線程,主內(nèi)存的值已經(jīng)被修改
@Test
public void seeOkByVolatile() {
MyData myData = new MyData();//資源類
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
//暫停一會線程
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
},"AAA").start();
//第2個線程是我們的main線程
while (myData.num == 0) {
//main線程就一直在這里等待循環(huán),直到num值不再等于0.
}
System.out.println(Thread.currentThread().getName() + "\t mission is over,main get num value: " + myData.num );
}
對num變量加volatile修飾后結(jié)果
AAA come in
AAA update num value: 60
main 我能見到AAA線程對num修改的結(jié)果啦,main get num value: 60Process finished with exit code 0
2.驗證volatile不保證原子性
2.1 原子性指的是什么意思?
不可分割,完整性,也即某個線程正在做某個具體任務(wù)時,中間不可以被加塞或者被分割。需要整體完整。要么同時成功,要么同時失敗。
2.2 volatile不保證原子性的案例演示
2.3 為什么不保證原子性?
2.4 如何保證原子性
加sync
使用我們juc下的AtomicInteger (底層實現(xiàn)CAS)
給MyData類加addPlusPlus()方法
class MyData {//MyData.java ===> MyData.class ===> JVM字節(jié)碼
int num = 0;
public void addT060() {
this.num = 60;
}
//請注意,此時num前面是加了關(guān)鍵字修飾的,volatile不保證原子性
public void addPlusPlus() {
num++;
}
}
2.2 volatile不保證原子性的案例演示
num++在多線程操作的情況下不保證原子性的
創(chuàng)建20個線程并行執(zhí)行num++操作2000次,多次測試,結(jié)果不為40000
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++ ) {
new Thread(() -> {
for (int j = 1; j <= 2000; j++) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20個線程都全部計算完成后,再用main線程取得最終的結(jié)果值看是多少?
while(Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally num value:" + myData.num);
}
結(jié)果:數(shù)值小于40000,出現(xiàn)寫值丟失的情況
main finally num value:38480
Process finished with exit code 0
2.3 為什么不保證原子性?
因為當(dāng)線程A對num++操作從自己的工作內(nèi)存刷新到主內(nèi)存時,還未通知到其他線程主內(nèi)存變量有更新的瞬間,其他線程對num變量的操作結(jié)果也對主內(nèi)存進行了刷新,從而導(dǎo)致了寫值丟失的情況
num++通過匯編指令分析,通過javap反編譯得到如下匯編指令
class com.slx.juc.MyData {
volatile int num;
com.slx.juc.MyData();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field num:I
9: return
public void addT060();
Code:
0: aload_0
1: bipush 60
3: putfield #2 // Field num:I
6: return
public void addPlusPlus();
Code:
0: aload_0
1: dup
2: getfield #2 // Field num:I
5: iconst_1
6: iadd
7: putfield #2 // Field num:I
10: return
}
可見num++被拆分成了3個步驟,簡稱:讀-改-寫
- 執(zhí)行g(shù)etfield拿到原始num;
- 執(zhí)行iadd進行加1操作;
- 執(zhí)行putfield寫把累加后的值寫回
2.4 如何保證原子性
加sync
使用我們juc下的AtomicInteger (底層實現(xiàn)CAS)
MyData類中添加原子類操作方法
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic() {
atomicInteger.getAndIncrement();
}
調(diào)用該方法打印結(jié)果
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++ ) {
new Thread(() -> {
for (int j = 1; j <= 2000; j++) {
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20個線程都全部計算完成后,再用main線程取得最終的結(jié)果值看是多少?
while(Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type ,finally num value:" + myData.atomicInteger);
}
測試結(jié)果為40000,不會出現(xiàn)之前int類型的丟失值的情況
main AtomicInteger type ,finally num value:40000
Process finished with exit code 0
總結(jié)
到此這篇關(guān)于Java中多線程與并發(fā)_volatile關(guān)鍵字的文章就介紹到這了,更多相關(guān)Java多線程與并發(fā)_volatile關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入學(xué)習(xí)JavaWeb中監(jiān)聽器(Listener)的使用方法
這篇文章主要為大家詳細介紹了深入學(xué)習(xí)JavaWeb中監(jiān)聽器(Listener)的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
Java求素數(shù)和最大公約數(shù)的簡單代碼示例
這篇文章主要介紹了Java求素數(shù)和最大公約數(shù)的簡單代碼示例,其中作者創(chuàng)建的Fraction類可以用來進行各種分數(shù)運算,需要的朋友可以參考下2015-09-09
Java實現(xiàn)EasyCaptcha圖形驗證碼的具體使用
Java圖形驗證碼,支持gif、中文、算術(shù)等類型,可用于Java Web、JavaSE等項目,下面就跟隨小編一起來了解一下2021-08-08

