Java多線程并發(fā)編程 Volatile關(guān)鍵字
volatile 關(guān)鍵字是一個神秘的關(guān)鍵字,也許在 J2EE 上的 JAVA 程序員會了解多一點,但在 Android 上的 JAVA 程序員大多不了解這個關(guān)鍵字。只要稍了解不當(dāng)就好容易導(dǎo)致一些并發(fā)上的錯誤發(fā)生,例如好多人把 volatile 理解成變量的鎖。(并不是)
volatile 的特性:
具備可見性
保證不同線程對被 volatile 修飾的變量的可見性。
有一被 volatile 修飾的變量 i,在一個線程中修改了此變量 i,對于其他線程來說 i 的修改是立即可見的。
如:
volatile int i = 0;// 語句 1 i++; // 語句 2
語句 2 執(zhí)行完后,i 最新的值會立即被強制更新到主內(nèi)存(共享內(nèi)存),并通知其他緩存了 i 的線程,令其他線程的工作內(nèi)存里的 i 失效,從而需重新到主內(nèi)存讀取最新的值。
具備有序性
被 volatile 修飾的變量,不會被優(yōu)化排序。
解決的問題詳見:Java 多線程并發(fā)編程 并發(fā)三大要素 的 三、有序性。
當(dāng)編譯器在給程序優(yōu)化排序時,若遇到 volatile 變量的讀操作或者寫操作,則會保證在其前面的操作全部進(jìn)行完成,且結(jié)果對后面的操作可見;并且保證在其后面的操作沒有進(jìn)行。
不具備原子性
volatile 不具備原子性,所以它是線程不安全的。
實驗:
// 一個單例的實現(xiàn) public class SingletonTest { private static volatile SingletonTest mInstance = null; private SingletonTest() {} public static SingletonTest getInstance() { if (mInstance == null) { mInstance = new SingletonTest(); System.out.println(" 初始化完成 "); } return mInstance; } } // 測試代碼 public class Test { public static void main(String[] var0) { for(int i = 0; i < 20; i++){ ThreadTest test = new ThreadTest(); test.start(); } } static class ThreadTest extends Thread{ @Override public void run() { super.run(); SingletonTest.getInstance(); } } }
結(jié)果:
每次運行都輸出多個 “初始化完成”。
volatile 的解釋
下面這段話摘自《深入理解 Java 虛擬機》:
“觀察加入 volatile 關(guān)鍵字和沒有加入 volatile 關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn),加入 volatile 關(guān)鍵字時,會多出一個 lock 前綴指令”
被 volatile 修飾的變量進(jìn)行讀和寫操作的時候,在相應(yīng)的匯編程序中都會多一句內(nèi)存屏障(Memory Barrier)。
而這個 lock 就是內(nèi)存屏障。
內(nèi)存屏障的作用:
1、在重新優(yōu)化排序時保證其后面的指令不會被排到內(nèi)存屏障的前面,前面的指令也不會排到內(nèi)存屏障的后面。- 有序性
2、強制對寫操作后的結(jié)果(立即)刷新到主內(nèi)存。
3、刷新結(jié)果到主內(nèi)存時,通知并令其他線程緩存內(nèi)的值過期 / 失效。
2 和 3 合起來則是可見性。
說到這里,也許會有好多人困惑,既然可見性可以保證,既然可以做到修改某個變量的值后,會刷新到主內(nèi)存,并令其他線程緩存失效,為什么不能保證原子性呢?這也是我之前走進(jìn)的一個困區(qū)。
繼續(xù)用 i++ 來分析一下,這里面包含的指令:
從主內(nèi)存讀取到緩存 // 指令 1
進(jìn)行運算 // 指令 2
從緩存刷新到主內(nèi)存 // 指令 3
內(nèi)存屏障 // 指令 4
雖然指令 4(內(nèi)存屏障)功能強大,但可惜 // 指令 1、2、3 都不是具備原子性,所以導(dǎo)致 volatile 不具備原子性,線程不安全,不能替代鎖的作用。
使用場景
如一些簡單的狀態(tài)標(biāo)記:
volatile boolean inited = false; // 線程 1 init(); // 語句 1 inited = true; // 語句 2 // 線程 2 while(inited){ work(); // 語句 3 }
1、可確保語句 1 和語句 2 的執(zhí)行順序。
2、可確保執(zhí)行語句 2 后,線程 2 可立即獲取到最新的修改,從而執(zhí)行語句 3。
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)之實現(xiàn)跳表
今天帶大家來學(xué)習(xí)Java數(shù)據(jù)結(jié)構(gòu)的相關(guān)知識,文中對用Java實現(xiàn)跳表作了非常詳細(xì)的圖文解說及代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05Springboot實現(xiàn)動態(tài)定時任務(wù)流程詳解
通過重寫SchedulingConfigurer方法實現(xiàn)對定時任務(wù)的操作,單次執(zhí)行、停止、啟動三個主要的基本功能,動態(tài)的從數(shù)據(jù)庫中獲取配置的定時任務(wù)cron信息,通過反射的方式靈活定位到具體的類與方法中2022-09-09mybatis 插件: 打印 sql 及其執(zhí)行時間實現(xiàn)方法
下面小編就為大家?guī)硪黄猰ybatis 插件: 打印 sql 及其執(zhí)行時間實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06java 中 System.out.println()和System.out.write()的區(qū)別
這篇文章主要介紹了 java 中 System.out.println()和System.out.write()的區(qū)別.的相關(guān)資料,需要的朋友可以參考下2017-04-04