您好,登錄后才能下訂單哦!
今天小編給大家分享一下Java中Volatile關鍵字能不能保證原子性的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
volatile 是 Java 中的一個相對來說比較重要的關鍵字,主要就是用來修飾會被不同線程訪問和修改的變量。
而這個變量只能保證兩個特性,一個是保證有序性,另外一個則是保證可見性。
那么什么是有序性,什么又是可見性呢?
那么什么是有序性呢?
其實程序執行的順序按照代碼的先后順序執行,禁止進行指令重排序。
看似理所當然,其實并不是這樣,指令重排序是JVM為了優化指令,提高程序運行效率,在不影響單線程程序執行結果的前提下,盡可能地提高并行度。
但是在多線程
環境下,有些代碼的順序改變,有可能引發邏輯上的不正確。
而 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。
如果大家對單例模式了解比較多的話,肯定也是關注過這個 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; } }
上面的單例模式大家熟悉么?
是的,這就是 **雙重檢查(DCL 懶漢式) **
有人會說,因為有指令重排序的存在,雙端檢索機制也也不一定是線程安全的呀,對呀,所以用到了 synchronized 關鍵字,讓他變成了線程安全的了。
其實可見性就是,在多線程環境中,對共享變量的修改對于其他線程是否立即可見的問題。
那么他的可見性一般都會表現在什么地方呢?用在什么地方呢?
其實一般用這個變量,很多都是為了保證他的可見性,就比如定義的一個全局變量,在其中有個循環來判斷這個變量的值,有一個線程修改了這個參數的時候,這個循環會停止,跳轉到之后去執行。
我們來看看沒有使用volatile修飾代碼實現:
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(); } }
結果大家肯定是可想而知,
運行結果肯定是:
線程A開始執行:
線程B開始執行
標識已經變更
確實,就是這樣的。
如果我們用 volatile 呢,那么這個代碼的執行結果就會不一樣呢?
我們來試一下:
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(); }
這樣我們就能看到另外一個執行結果,在循環當中的輸出語句是可以被執行的。
也就是說,在線程B 中,我們去修改這個被修飾的變量,那么最終,在線程A中,就能順利讀取到我們的數據信息了。
不能,我們來看一點代碼,被volatile修飾的變量,
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); } }
如果能夠保原子性,那么最終的結果應該是20000,但是每次的最終結果并不能保證就是20000,比如:
main final number result = 17114
main final number result = 20000
main final number result = 19317
三次執行,都是不同的結果,
為什么會出現這種呢?這就和number++有關系了
number++被拆分成3個指令
執行GETFIELD拿到主內存中的原始值number
執行IADD進行加1操作
執行PUTFIELD把工作內存中的值寫回主內存中
當多個線程并發執行PUTFIELD指令的時候,會出現寫回主內存覆蓋問題,所以才會導致最終結果不為 20000,所以 volatile 不能保證原子性。
以上就是“Java中Volatile關鍵字能不能保證原子性”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。