volatile 是 Java 中的一個相對來說比較重要的關鍵字,主要是用來修飾會被不同執行緒存取和修改的變數。
而這個變數只能保證兩個特性,一個是保證有序性,另外一個則是保證可見性。
那麼什麼是有序性,什麼又是可見性呢?
那麼什麼是有序性呢?
其實程式執行的順序是依照程式碼的先後順序執行,禁止進行指令重新排序。
看似理所當然,其實不是這樣,指令重排序是JVM為了優化指令,提高程式運作效率,在不影響單執行緒程式執行結果的前提下,盡可能地提高並行度。
但是在多執行緒
環境下,有些程式碼的順序改變,有可能引發邏輯上的不正確。
而 volatile 就是因為有這個特性,所以才被大家熟知的。
volatile 又是如何保證有序性的呢?
有很多小夥伴就說,網路上說的是 volatile 可以禁止指令指令重排序,這就保證了程式碼的程式會嚴格按照程式碼的先後順序執行。這就保證了有序性。被 volatile 修飾的變數的操作,會嚴格按照程式碼順序執行,就是說當程式碼執行到 volatile 修飾的變數時,其前面的程式碼一定執行完畢,後面的程式碼一定沒有執行。
如果這時候,面試官不再繼續深挖下去的話,那麼恭喜你,可能這個問題已經回答完了,但是如果面試官繼續往下深挖,為什麼會禁止指令重排,什麼又是指令重排呢?
在從原始碼到指令的執行,一般是分成了三種重排,如圖:
#'接下來就得看看volatile 是如何禁止指令重排的。
我們直接用程式碼來驗證:
如果大家對單例模式了解比較多的話,肯定也是關注過這個 volatile,為什麼呢?public class ReSortDemo { int a = 0; boolean flag = false; public void mehtod1(){ a = 1; flag = true; } public void method2(){ if(flag){ a = a +1; System.out.println("最后的值: "+a); } } }登入後複製如果有人看到這段程式碼,一定會說,那這段程式碼出來的結果會是什麼呢?
有些人說是2,是的, 如果你只是單線程調用,那結果就是2,但是如果是多線程調用的時候,最後的輸出結果不一定是我們想像到的2,這時就要把兩個變數都設定為volatile。
大家看看如下程式碼:
###class Singleton { // 不是一个原子性操作 //private static Singleton instance; //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生! private static volatile Singleton instance; // 构造器私有化 private Singleton() { } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
public class Test { private static boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); } }
public class Test { private static volatile boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); }
public class Test { // volatile不保证原子性 // 原子性:保证数据一致性、完整性 volatile int number = 0; public void addPlusPlus() { number++; } public static void main(String[] args) { Test volatileAtomDemo = new Test(); for (int j = 0; j < 20; j++) { new Thread(() -> { for (int i = 0; i < 1000; i++) { volatileAtomDemo.addPlusPlus(); } }, String.valueOf(j)).start(); }// 后台默认两个线程:一个是main线程,一个是gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000 System.out.println(Thread.currentThread().getName() + " final number result = " + volatileAtomDemo.number); } }
number 被拆分成3個指令
執行GETFIELD拿到主記憶體中的原始值number
執行IADD進行加1操作
執行PUTFIELD把工作記憶體中的值寫回主記憶體中
當多個執行緒並發執行PUTFIELD指令的時候,會出現寫回主記憶體覆蓋問題,所以才會導致最終結果不為20000,所以volatile 不能保證原子性。
以上是Java中的Volatile關鍵字能否保證執行緒安全?的詳細內容。更多資訊請關注PHP中文網其他相關文章!