android 多線程 — volatile

ps: 老文章拆分出來的內(nèi)容

并發(fā)三原則

只有保證了這三點才能在并發(fā)中獲得想要的結果呕臂,我們想要進一步了解多線程朗儒,這3個原則必須要明白,并發(fā)都是圍繞這2個原則展開的

簡單來說是這樣的

可見性 - 是指線程之間的可見性吮炕,一個線程修改的狀態(tài)對另一個線程是可見的腊脱。也就是一個線程修改的結果,另一個線程馬上就能看到来屠。簡單來說虑椎,線程的私有內(nèi)存中對象副本和主內(nèi)存對象的數(shù)據(jù)之間就是可見性問題,線程不把他私有內(nèi)存中的對象副本協(xié)會到主內(nèi)存中俱笛,那么對于其他想操作這個對象的線程來說就是"不可見的" 捆姜,最新數(shù)據(jù)不可見,所以此時其他線程可以獲取該對象的舊數(shù)據(jù)

原子性 - 對基本類型變量的讀取和賦值操作是原子性操作迎膜,即這些操作是不可中斷的泥技,要么執(zhí)行完畢,要么就不執(zhí)行磕仅。在多線程中珊豹,原子性是線程不安全的,其實和可見性有關聯(lián)榕订,線程在計算數(shù)據(jù)時會把在自己的工作區(qū)域 copy 一份數(shù)據(jù)的副本店茶,然后計算的是這個數(shù)據(jù)的副本,最后再把數(shù)據(jù)學會內(nèi)存中劫恒,這個過程里要是有別人線程同時操作同一個數(shù)據(jù)贩幻,那么我們的計算結果就是不正確了轿腺,就像打電話串線一樣,結果肯定不對

x =3;    
y =4;    
z = x+y;
x++丛楚;  

上面第三行就包括了多個操作族壳,1是先讀取x的值,2讀取y的值趣些,3將計算中的值仿荆,4把z的值寫回內(nèi)存。一般一個語句含有多個操作該語句就不是原子性的操作坏平,只有最簡單的讀取和賦值才是原子性的操作

有序性 - 即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行拢操,上面講了因為原子性問題,絕部分操作都是可以再分的舶替,分成多個操作庐冯,這其中有對數(shù)據(jù)的讀,改坎穿,寫等展父,這些操作速度不一,JVM 為了提高效率玲昧,在保證結果相同的前提下栖茉,有計劃的多這么操作分組,在執(zhí)行需要等待的操作中孵延,穿插執(zhí)行其他執(zhí)行速度塊的操作吕漂,這叫指令重排序

指令重排序指的是在 保證程序最終執(zhí)行結果和代碼順序執(zhí)行的結果一致的前提下,改變語句執(zhí)行的順序來優(yōu)化輸入代碼尘应,提高程序運行效率惶凝。

重排序在單線程中沒啥問題,咱們等著執(zhí)行結果唄犬钢,反正重排序保證結果正確苍鲜。但是在多先撤我那個環(huán)境下是存在并發(fā)的,你這里對某一個對象的執(zhí)行重排序了玷犹,但是不是瞬間完成的混滔,這時另外的線程可以操作這個對象,那么你這個重排序后的執(zhí)行可能造成此時對象數(shù)據(jù)的不正確歹颓,會對其他線程使用這個對象產(chǎn)生影響

以下面的舉個例子:

int i = 0;              
boolean flag = false;
i = 1; //語句1           
flag = true; //語句2

定義了一個整形和Boolean型變量坯屿,并通過語句1和語句2對這兩個變量賦值,但是JVM在執(zhí)行這段代碼的時候并不保證語句1在語句2之前執(zhí)行巍扛,也就是說可能會發(fā)生 指令重排序领跛。

再來個例子:

//線程1:
context = loadContext(); //語句1
inited = true; //語句2
 
//線程2:
while (!inited) {
    sleep()
}

doSomethingWithConfig(context);

對于線程1來說,語句1和語句2沒有依賴關系撤奸,因此有可能會發(fā)生指令重排序的情況吠昭。但是對于線程2來說鹅经,語句2在語句1之前執(zhí)行,那么就會導致進入doSomethingWithConfig函數(shù)的時候context沒有初始化

Java 提供了3個關鍵字 volatile怎诫、synchronized 和 final 來實現(xiàn)并發(fā)3原則

  • final - 最好理解,一切都是不可變的贷痪,所以不在乎有多少個線程同時操作這個資源
  • synchronized - 之前的文章介紹了幻妓,synchronized 保證了有序性,你想 synchronized 使用一把鎖鎖住了資源劫拢,那別人想用只能等著肉津,即便你再怎么重排序,我也能保證執(zhí)行效果
  • volatile - 就比較復雜了舱沧,也是本文的重點

volatile

Volatile 是面試官最愛文的妹沙,即便你是做 android 開發(fā)的,你也逃不出去熟吏,所以大家好好鉆研吧

volatile 的特性: 先是非同步的 -> 保證了可見性 -> 同時也保證有序性 -> 但是不保證原子性

  • 非同步 - volatile 修飾的變量不是 synchronized 的距糖,不是同步的,同一時間是能被多個線程操作的牵寺,所以 volatile 的使用范圍比較窄悍引,多用于修飾 static 靜態(tài)變量

  • 保證可見性 - 好多地方都說 volatile 修飾的變量,線程直接和內(nèi)存交互帽氓,不會保存副本趣斤,而實際上線程還是會保存副本,只不過 CPU 每次都會從內(nèi)存中拿到最新的值黎休,并且改變數(shù)據(jù)之后立馬寫回內(nèi)存并通知其他改數(shù)據(jù)的備份數(shù)據(jù)改變了浓领,看上去就像線程直接和內(nèi)存交互一樣

  • 不保證原子性 - volatile 語義并不能保證變量的原子性。對任意單個volatile變量的讀/寫具有原子性势腮,但類似于i++联贩、i–這種復合操作不具有原子性,因為自增運算包括讀取i的值捎拯、i值增加1撑蒜、重新賦值3步操作,并不具備原子性

  • 保證有序性 - volatile 能夠屏蔽指令重排序:

    • 當程序執(zhí)行到volatile變量的讀操作或者寫操作時玄渗,在其前面的操作的更改肯定全部已經(jīng)進行座菠,且結果已經(jīng)對后面的操作可見;在其后面的操作肯定還沒有進行藤树;
    • 在進行指令優(yōu)化時浴滴,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,也不能把volatile變量后面的語句放到其前面執(zhí)行岁钓。

volatile 適用場景:

  • 禁止系統(tǒng)重排序的情況
  • 只有一個線程寫升略,多個線程讀的情況
  • 關鍵的標記行參數(shù)
  • 靜態(tài)單例

當然了 volatile 最經(jīng)典的用處就是單例了

public class RxBus {

    public static volatile RxBus instance;

    public static RxBus getInstance() {
        if (instance == null) {
            synchronized (RxBus.class) {
                if (instance == null) {
                    instance = new RxBus();
                }
            }
        }
        return instance;
    }
}

我們對于靜態(tài)單例使用了 volatile 就能保證整個方法的執(zhí)行順序是按照我們縮寫的執(zhí)行微王。

若是我們不加 volatile ,在多線程時指令重排序品嚣,一個線程發(fā)現(xiàn) instance 是 null 的就會 new 一個對象出來炕倘,此時因為指令沖排序,很可能先在內(nèi)存 new 一塊空間然后賦值給 instance 翰撑,然后再去執(zhí)行實例化對象的操作罩旋,對象實例化的操作是比較重的。這是領一額個線程進來眶诈,發(fā)現(xiàn) instance 不是 null 涨醋,然后就去執(zhí)行代碼,但是此時 instance 實際只是有了一塊內(nèi)存地址逝撬,但是對象本身還沒初始化浴骂,就會產(chǎn)生空指針的問題

volatile 的優(yōu)勢是同步性能開銷比鎖低很多,若是使用 synchronized + 鎖宪潮,切換鎖給不同的線程要好幾毫秒溯警,比 new 個線程對象都耗費時間多了

但是 volatile 也有很嚴重的問題,那就是 volatile 不能保證原子性狡相,雖然 volatile 讓內(nèi)存可以同步到所有地方愧膀,但是并不能阻止多個線程同時操作同一個數(shù)據(jù),是沒法保證原子性的谣光,所以是不能代替 synchronized + 鎖檩淋,因此我們在使用 volatile 要及其小心,要思考會不會帶來并發(fā)問題萄金,一般我們見到的 volatile 應用都很少蟀悦,也都很死,都是固定的幾個場景使用


參考文檔:

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氧敢,一起剝皮案震驚了整個濱河市日戈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孙乖,老刑警劉巖浙炼,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唯袄,居然都是意外死亡弯屈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門恋拷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來资厉,“玉大人,你說我怎么就攤上這事蔬顾⊙绯ィ” “怎么了湘捎?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窄刘。 經(jīng)常有香客問我窥妇,道長,這世上最難降的妖魔是什么娩践? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任活翩,我火速辦了婚禮,結果婚禮上欺矫,老公的妹妹穿的比我還像新娘。我一直安慰自己展氓,他們只是感情好穆趴,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遇汞,像睡著了一般未妹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上空入,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天络它,我揣著相機與錄音,去河邊找鬼歪赢。 笑死化戳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的埋凯。 我是一名探鬼主播点楼,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼白对!你這毒婦竟也來了掠廓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤甩恼,失蹤者是張志新(化名)和其女友劉穎蟀瞧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體条摸,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡悦污,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钉蒲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塞关。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖子巾,靈堂內(nèi)的尸體忽然破棺而出帆赢,到底是詐尸還是另有隱情小压,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布椰于,位于F島的核電站怠益,受9級特大地震影響,放射性物質發(fā)生泄漏瘾婿。R本人自食惡果不足惜蜻牢,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偏陪。 院中可真熱鬧抢呆,春花似錦、人聲如沸笛谦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饥脑。三九已至恳邀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灶轰,已是汗流浹背谣沸。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笋颤,地道東北人乳附。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像伴澄,于是被迫代替她去往敵國和親许溅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容