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驗證
下面代碼: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学习(可见性,不保证原子性,禁止指令重排(双端检索机制))的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 基础学习笔记 C02【列
- 下一篇: PWN-PRACTICE-BUUCTF-