volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))
volatile是java虛擬機提供的輕量級的同步機制:
1.保證可見性:線程之間可見性(及時通知)
2.不保證原子性
3.禁止指令重排
先了解一下jvm同步
由于JVM運行程序的實體是線程,而每個線程創(chuàng)建時JVM都會為其創(chuàng)建一個工作內(nèi)存(或者稱為棧空間),工作內(nèi)存是每個線程的私有數(shù)據(jù)區(qū)域,而java內(nèi)存模型中規(guī)定所有變量都存儲在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內(nèi)存中進行,首先要將變量從主內(nèi)存拷貝到自己的棧空間,然后對變量進行操作,操作完成后再將變量寫回主內(nèi)存,不能直接操作主內(nèi)存中的變量,各個線程中的工作內(nèi)存中存儲著主內(nèi)存中的變量副本拷貝,因此不同的線程間無法訪問對方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成。
一、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){//循環(huán)等待}System.out.println(Thread.currentThread().getName());} }運行結(jié)果可以看出來會卡在while循環(huán)處
二、加上volatile后
結(jié)果:
結(jié)果可以看出,當(dāng)其他線程修改了主內(nèi)存空間的值時,加上了volatile主內(nèi)存空間的值改變后會及時通知其他線程主物理內(nèi)存的值被修改。
二:不保證原子性demo驗證
下面代碼:20個線程,每個線程進行1000次number++,理論上結(jié)果是兩萬,實際運行:
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);}結(jié)果并不是兩萬:所以說不能保證原子性,不能保證結(jié)果一致性,存在線程安全問題
解決辦法:
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相關(guān)內(nèi)容可以看API了解方法怎么用。)就可以保證原子性。
三、禁止指令重排
理解這里需要了解一點編譯器,編譯器在編譯時,會有個優(yōu)化指令重排,在多線程下指令重排會造成線程安全問題,最終一致性無法保證
多線程環(huán)境中線程的交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個線程中使用的變量能否保證一致性是無法確定的,結(jié)果無法預(yù)測。
例:單例模式為例(在多線程下,普通的單例模式并不適用)
class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調(diào)用構(gòu)造方法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();}} }結(jié)果:可以看到并不是只會創(chuàng)建一個對象
使用dlc(Double Check Lock雙端檢索機制),代碼如下
class SingletonDemo{private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調(diào)用構(gòu)造方法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();}} }結(jié)果:
那么這樣就可以了嗎?
不行,因為編譯器會進行指令重排,instance = new SingletonDemo();編譯器會拆分成三步,
1.memory = allocate(); //分配對象內(nèi)存空間
2.instance(memory);//初始化對象
3.instance = memory; //設(shè)置instance指向剛分配的內(nèi)存地址,此時instace!=null
所以可能出現(xiàn),另一個線程進入了第一個if (instance == null){時,instance的引用對象還未初始化完成,所以要加入volatile來禁止指令重排
package Volatile;class SingletonDemo{private static volatile SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t"+"調(diào)用構(gòu)造方法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();}} }總結(jié)
以上是生活随笔為你收集整理的volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 基础学习笔记 C02【列
- 下一篇: iPhone 14 Pro古铜配色曝光霸