JVM synchronized和volatile 關(guān)鍵字

本編文章都是基于下圖這個(gè),計(jì)算機(jī)cpu 贱纠、緩存峻厚、內(nèi)存、線程之間的關(guān)系谆焊;


無標(biāo)題.png

一惠桃、緩存一致性問題

計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過程中辜王,勢(shì)必涉及到數(shù)據(jù)的讀取和寫入劈狐。由于程序運(yùn)行過程中的臨時(shí)數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的,這時(shí)就存在一個(gè)問題呐馆,由于CPU執(zhí)行速度很快肥缔,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多,因此如果任何時(shí)候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進(jìn)行汹来,會(huì)大大降低指令執(zhí)行的速度续膳。因此在CPU里面就有了高速緩存。

當(dāng)程序在運(yùn)行過程中收班,會(huì)將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中坟岔,那么CPU進(jìn)行計(jì)算時(shí)就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當(dāng)運(yùn)算結(jié)束之后摔桦,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中炮车。

中間的高速緩存就是cpu和內(nèi)存的中間過程。
但是在多線程酣溃,每個(gè)線程在不同的cpu中運(yùn)行時(shí)瘦穆,每個(gè)線程分別讀取內(nèi)存中的值存入各自所在的CPU的高速緩存當(dāng)中,cpu對(duì)數(shù)據(jù)改變后赊豌,就造成了緩存一致性的問題扛或,通常稱這種被多個(gè)線程訪問的變量為共享變量。

也就是說碘饼,如果一個(gè)變量在多個(gè)CPU中都存在緩存(一般在多線程編程時(shí)才會(huì)出現(xiàn))熙兔,那么就可能存在緩存不一致的問題。

為了解決緩存不一致性問題艾恼,通常來說有以下2種解決方法:

1)通過synchronized鎖的方式
2)通過緩存一致性協(xié)議

二住涉、并發(fā)編程中的三個(gè)概念

  • 原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷,要么就都不執(zhí)行钠绍,相當(dāng)于事物的概念舆声。

  • 可見性:可見性是指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值柳爽,其他線程能夠立即看得到修改的值媳握。

  • 有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,因?yàn)橛兄噶钪嘏判騿栴}磷脯;
    指令重排序蛾找,一般來說,處理器為了提高程序運(yùn)行效率赵誓,可能會(huì)對(duì)輸入代碼進(jìn)行優(yōu)化打毛,它不保證程序中各個(gè)語句的執(zhí)行先后順序同代碼中的順序一致柿赊,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。這種情況在單線程下沒有問題幻枉,但是在多線程下有可能出現(xiàn)問題闹瞧。

也就是說,要想并發(fā)程序正確地執(zhí)行展辞,必須要保證原子性、可見性以及有序性万牺。只要有一個(gè)沒有被保證罗珍,就有可能會(huì)導(dǎo)致程序運(yùn)行不正確。

三脚粟、synchronized

同步鎖可以保證并發(fā)編程中的原子性覆旱、可見性、有序性核无;但是效率比較低扣唱。

四、volatile

  1. 保證可見性問題:
    一個(gè)共享變量被volatile修飾時(shí)团南,當(dāng)CPU對(duì)該變量有寫操作時(shí)噪沙,它會(huì)保證修改的值會(huì)立即被更新到主內(nèi)存中,并會(huì)發(fā)出信號(hào)通知其他CPU將該變量的緩存設(shè)置為無效狀態(tài)吐根,因此當(dāng)其他CPU需要讀取這個(gè)變量時(shí)正歼,發(fā)現(xiàn)自己的高速緩存中該變量是無效的,那么它就會(huì)從內(nèi)存重新讀取拷橘。

    而普通的共享變量不能保證可見性局义,因?yàn)槠胀ü蚕碜兞勘恍薷闹螅裁磿r(shí)候被寫入主存是不確定的冗疮,當(dāng)其他線程去讀取時(shí)萄唇,此時(shí)內(nèi)存中可能還是原來的舊值,因此無法保證可見性术幔。

    可見性只能保證每次讀取的是最新的值

  1. 保證有序性問題:
    當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí)另萤,在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見诅挑;在其后面的操作肯定還沒有進(jìn)行仲墨;

    在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量訪問的語句放在其后面執(zhí)行揍障,也不能把volatile變量后面的語句放到其前面執(zhí)行

  2. 不能保證原子性問題:
    測試代碼:

package com.test.jvm;

public class Test {
  
  public volatile int i = 0;  
  public void increase(){   //可以添加關(guān)鍵字synchronized看結(jié)果不同
      i++;
  }
  
  public static void main(String[] args) {        
      final Test test = new Test();
      for(int x =0; x<10; x++){
          new Thread(){@Override
              public void run() {
                  for(int y=0; y<1000; y++){
                      test.increase();
                  }
              }
          }.start();
      }
          
      while(Thread.activeCount()>1){  //保證前面的線程都執(zhí)行完
          Thread.yield();
          System.out.println(test.i);
       }      
  }

}

最后i的結(jié)果并不是10000 目养,總是小于10000;
解釋:
假如某個(gè)時(shí)刻變量 i 的值為10毒嫡,

cpu1中線程A對(duì)變量進(jìn)行自增操作癌蚁,線程A先讀取了變量 i 的原始值幻梯,然后線程A被阻塞了;

然后cpu2中線程B對(duì)變量進(jìn)行自增操作努释,線程B也去讀取變量 i 的原始值碘梢,由于線程A只是對(duì)變量 i 進(jìn)行讀取操作,而沒有對(duì)變量進(jìn)行修改操作伐蒂,所以不會(huì)導(dǎo)致線程B的工作內(nèi)存中緩存變量 i 的緩存無效煞躬,所以線程B中 i 的值10,然后進(jìn)行加1操作逸邦,并把11寫入工作內(nèi)存恩沛,最后寫入主存。

然后線程A接著進(jìn)行加1操作缕减,由于已經(jīng)讀取了 i 的值雷客,注意此時(shí)在線程A的工作內(nèi)存中 i
的值仍然為10,所以線程A對(duì) i 進(jìn)行加1操作后 i 的值為11桥狡,然后將11寫入工作內(nèi)存搅裙,最后寫入主存。

那么兩個(gè)線程分別進(jìn)行了一次自增操作后裹芝,i 只增加了1部逮。

解釋到這里,可能有朋友會(huì)有疑問嫂易,不對(duì)啊甥啄,前面不是保證一個(gè)變量在修改volatile變量時(shí),會(huì)讓緩存行無效嗎炬搭?然后其他線程去讀就會(huì)讀到新的值蜈漓,對(duì),這個(gè)沒錯(cuò)宫盔。但是要注意融虽,線程A對(duì)變量進(jìn)行讀取操作之后,被阻塞了的話灼芭,并沒有對(duì) i 值進(jìn)行修改有额。然后雖然volatile能保證線程B對(duì)變量 i 的值讀取是從內(nèi)存中讀取的,但是線程A沒有進(jìn)行修改彼绷,所以線程B根本就不會(huì)看到修改的值巍佑。

總結(jié):

使用volatitle關(guān)鍵字要保證的兩個(gè)條件:
1) 對(duì)變量的寫操作不依賴于當(dāng)前值
2) 該變量沒有包含在其他變量中

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寄悯,隨后出現(xiàn)的幾起案子萤衰,更是在濱河造成了極大的恐慌,老刑警劉巖猜旬,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脆栋,死亡現(xiàn)場離奇詭異倦卖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)椿争,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門怕膛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秦踪,你說我怎么就攤上這事褐捻。” “怎么了椅邓?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵柠逞,是天一觀的道長。 經(jīng)常有香客問我希坚,道長,這世上最難降的妖魔是什么陵且? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任裁僧,我火速辦了婚禮,結(jié)果婚禮上慕购,老公的妹妹穿的比我還像新娘聊疲。我一直安慰自己,他們只是感情好沪悲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布获洲。 她就那樣靜靜地躺著,像睡著了一般殿如。 火紅的嫁衣襯著肌膚如雪贡珊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天涉馁,我揣著相機(jī)與錄音门岔,去河邊找鬼。 笑死烤送,一個(gè)胖子當(dāng)著我的面吹牛寒随,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帮坚,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妻往,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了试和?” 一聲冷哼從身側(cè)響起讯泣,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阅悍,沒想到半個(gè)月后局嘁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悦昵,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年连躏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了入热。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺良。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蠢箩,死狀恐怖谬泌,靈堂內(nèi)的尸體忽然破棺而出呵萨,到底是詐尸還是另有隱情,我是刑警寧澤忱嘹,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站分苇,受9級(jí)特大地震影響医寿,放射性物質(zhì)發(fā)生泄漏靖秩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧罐农,春花似錦、人聲如沸蒲凶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敏弃。三九已至绿饵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間要门,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓堆生,卻偏偏與公主長得像涝婉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔗怠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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