日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))

發布時間:2023/12/10 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制)) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

volatile是java虛擬機提供的輕量級的同步機制:

1.保證可見性:線程之間可見性(及時通知)
2.不保證原子性
3.禁止指令重排

先了解一下jvm同步
由于JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(或者稱為棧空間),工作內存是每個線程的私有數據區域,而java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝到自己的棧空間,然后對變量進行操作,操作完成后再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存中存儲著主內存中的變量副本拷貝,因此不同的線程間無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成。

一、volatile的可見性demo驗證

一、沒有加volatile

package Volatile;import java.util.concurrent.TimeUnit;/*** volatile的可見性* demo*/ class MyData{int number = 0;public void addTO60(){this.number = 60;} }public class demo {public static void main(String[] args) {MyData myData = new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "come in");//讓線程等待3stry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTO60();System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);},"AAA").start();//第二個線程是main線程while(myData.number == 0){//循環等待}System.out.println(Thread.currentThread().getName());} }

運行結果可以看出來會卡在while循環處

二、加上volatile后

/*** volatile的可見性* demo*/ class MyData{volatile int number = 0;public void addTO60(){this.number = 60;} }public class demo {public static void main(String[] args) {MyData myData = new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "come in");//讓線程等待3stry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTO60();System.out.println(Thread.currentThread().getName() + "updated number value:" + myData.number);},"AAA").start();//第二個線程是main線程while(myData.number == 0){//循環等待}System.out.println(Thread.currentThread().getName()+"mission is over;main get number value:"+myData.number);} }

結果:

結果可以看出,當其他線程修改了主內存空間的值時,加上了volatile主內存空間的值改變后會及時通知其他線程主物理內存的值被修改。

二:不保證原子性demo驗證

下面代碼:20個線程,每個線程進行1000次number++,理論上結果是兩萬,實際運行:

public static void main(String[] args) {MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 0; j <1000 ; j++) {myData.addPlusPlus();}},String.valueOf(i)).start();}while(Thread.activeCount()>2){Thread.yield();}System.out.println(myData.number);}

結果并不是兩萬:所以說不能保證原子性,不能保證結果一致性,存在線程安全問題

解決辦法:

1.synchronized(有點小題大做)
synchronized public void addPlusPlus() {this.number++; }
2.使用AtomicInteger
class MyData {volatile int number = 0;public void addTO60() {this.number = 60;}public void addPlusPlus() {this.number++;}AtomicInteger atomicInteger = new AtomicInteger();public void atomicPlusPlus(){atomicInteger.getAndIncrement();} } public class demo {public static void main(String[] args) {MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 0; j <1000 ; j++) {myData.addPlusPlus();myData.atomicPlusPlus();}},String.valueOf(i)).start();}while(Thread.activeCount()>2){Thread.yield();}System.out.println(myData.number);System.out.println(myData.atomicInteger);}

就是用AtomicInteger來代替number,用getAndIncrement來代替number++(Atomic相關內容可以看API了解方法怎么用。)就可以保證原子性。

三、禁止指令重排

理解這里需要了解一點編譯器,編譯器在編譯時,會有個優化指令重排,在多線程下指令重排會造成線程安全問題,最終一致性無法保證

多線程環境中線程的交替執行,由于編譯器優化重排的存在,兩個線程中使用的變量能否保證一致性是無法確定的,結果無法預測。

例:單例模式為例(在多線程下,普通的單例模式并不適用)

class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調用構造方法SingletonDemo");}public static SingletonDemo getInstance(){if (instance == null){instance = new SingletonDemo();}return instance;} } public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}} }

結果:可以看到并不是只會創建一個對象

使用dlc(Double Check Lock雙端檢索機制),代碼如下

class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調用構造方法SingletonDemo");}// public static SingletonDemo getInstance(){ // if (instance == null){ // instance = new SingletonDemo(); // } // return instance; // }//使用dlc雙端檢索機制public static SingletonDemo getInstance(){if (instance == null){synchronized(SingletonDemo.class){if (instance == null){instance = new SingletonDemo();}}}return instance;} } public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}} }

結果:

那么這樣就可以了嗎?

不行,因為編譯器會進行指令重排,instance = new SingletonDemo();編譯器會拆分成三步,
1.memory = allocate(); //分配對象內存空間
2.instance(memory);//初始化對象
3.instance = memory; //設置instance指向剛分配的內存地址,此時instace!=null

所以可能出現,另一個線程進入了第一個if (instance == null){時,instance的引用對象還未初始化完成,所以要加入volatile來禁止指令重排

package Volatile;class SingletonDemo{private static volatile SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調用構造方法SingletonDemo");}// public static SingletonDemo getInstance(){ // if (instance == null){ // instance = new SingletonDemo(); // } // return instance; // }//使用dlc雙端檢索機制public static SingletonDemo getInstance(){if (instance == null){synchronized(SingletonDemo.class){if (instance == null){instance = new SingletonDemo();}}}return instance;} } public class demo1 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}} }

總結

以上是生活随笔為你收集整理的volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。