Java 線程并發之synchronized和Volatile

每個線程都有自己的執行空間(即工作內存),線程執行的時候用到某變量,首先要將變量從主內存拷貝的自己的工作內存空間,然后對變量進行操作:讀取,修改,賦值等,這些均在工作內存完成,操作完成后再將變量寫回主內存。當多個線程同時讀寫某個內存數據時,就會涉及到線程并發的問題。涉及到三個特 性:原子性,有序性,可見性。
簡單說下這個三個特性的概念:
switch(線程特性){
case (可見性):
一個數據在多個線程中都存在副本的時候,任何一個線程對共享變量的修改,其它線程都應該看到被修改之后的值。
break;
case(有序性):
線程的有序性即按代碼的先后順序執行。很經典的就是銀行的存錢取錢問題,比如A線程負責取錢,B線程負責取錢,賬戶里面有100塊,這時候B和A都讀取了賬戶余額,100塊,這時B取出了10塊,寫入主內存后這時候賬戶還有90塊,但A知道的是100塊然后存了10塊,再寫入主內存就是110塊,這顯然是不對的,沒有保存線程的有序性。
break;
case ( 原子性):
原子性是指一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其它線程干擾。
Java中的原子操作包括:
1)除long和double之外的基本類型的賦值操作
2)所有引用reference的賦值操作
3)java.concurrent.Atomic.* 包中所有類的一切操作。
線程之間的交互只能通過共享數據來實現,而不能直接傳遞數據。
同步是為了解決多個線程對共享數據的訪問和操作混亂達不到預期效果這種情況而引入的機制。
break;
}

Synchronized

看如下代碼:在main方法中:

for (int index = 0; index < 3; index++) {
    new Thread() {   
     @Override
public void run() {  
for (int i = 0; i < 1000; i++) { 
try {                 
   Thread.sleep(1);       
         } catch (InterruptedException e) {      
              e.printStackTrace();                }         
       incTestNum();   }    
    }    }.start();
}
new Thread() { 
   @Override   
 public void run() { 
       for (int i = 0; i < 300; i++) {    
        try {             
   Thread.sleep(10);        
    } catch (InterruptedException e) {    
            e.printStackTrace();            }    
        getTestNum();        } 
   }}.start();
private static void incTestNum() {  
  i++;    j++;}
private static void getTestNum() {   
 System.out.println("i===========" + i + ";j============" + j);}

我們得到 的結果是:

i===========63;j============63
i===========93;j============93
i===========121;j============122
i===========151;j============152
i===========180;j============182
i===========210;j============212
i===========240;j============242
i===========267;j============270

可以看到j有可能會比i大,這是在多線程并發操作i和j 的時候,并沒有同步線程,此時同時操作i和j不具有原子性。并且i++本身也不是原子操作,先讀取i,再執行i+1;然后再賦值給i;然后再寫入內存。但直觀來說應該i比j大,這是應該在讀取i之后和讀取j之前加操作又執行了多次。導致看到的j比i大。
當我們加上在線程里面加上synchronized之后:

private synchronized static void incTestNum() {   
 i++;    j++;}
private synchronized static void getTestNum() {    
System.out.println("i===========" + i + ";j============" + j);}

結果:

i===========92;j============92
i===========121;j============121
i===========150;j============150
i===========182;j============182
i===========209;j============209
i===========240;j============240
i===========269;j============269
......
i===========3000;j============3000

從結果可以看出,用synchronized鎖住的方法是同步執行的,并且得到了正確的結果。
使用synchronized修飾的方法或者代碼塊可以看成是一個原子操作。如果一個線程獲取了鎖,其它線程需要獲取鎖來執行的時候,其它線程就進入了等待鎖的釋放。這個過程是阻塞的。
一個線程執行互斥代碼過程如下:

  1. 獲得同步鎖;
  2. 清空工作內存;
  3. 從主內存拷貝對象副本到工作內存;
  4. 執行代碼(計算或者輸出等);
  5. 刷新主內存數據;
  6. 釋放同步鎖。
    所以,synchronized既保證了多線程的并發有序性,又保證了多線程的內存可見性。

如果在靜態方法上加synchronized,那么作用等同于:

void method{
   synchronized(Obl.class)
   }
}

既然要同步我們就要用線程之間的同享對象作為鎖,所以下面方式是錯誤的使用:

Object lock=new Object();
synchronized(lock){

}

使用同步方法的時候:

private synchronized  void test(){    }

等價于:

private void test(){
synchronized(this){
}
}

jdK1.5之后,對synchronized同步機制做了很多優化,如:自適應的自旋鎖、鎖粗化、鎖消除、輕量級鎖等,使得它的性能明顯有了很大的提升。

volatile

關于volatile的實現原理可以看看這篇文章:
深入分析Volatile的實現原理
volatile告訴jvm, 它所修飾的變量不保留拷貝,直接訪問主內存中的。這就保證了可見性
我們在上面個例子的共享變量加上volatile關鍵:

static volatile int i = 0, j = 0;

再來看看運行結果:

i===========241;j============241
i===========272;j============271
i===========301;j============301
i===========329;j============330
i===========359;j============360
i===========390;j============390
......
i===========2984;j============2993

這看起來加了volatile和沒有加是一樣的效果,看起來線程都沒有同步。原因是volatile不能保證操作的原子性。也就不能保證i++和j++的原子性,當A,B線程讀取i的值假設此時i=10,然后A線程執行+1再寫入,刷入主內存中,此時主內存i的值是11,然后現在B線程再執行+1寫入主內存,此時主內存中i的值被還是11,但此時正常情況應該是12 的。這就是為什么最后的結果i和j都比3000小。
聲明為volatile的簡單變量如果當前值與該變量以前的值相關,那么volatile關鍵字不起作用。如i++;i=i+1;
當且僅當滿足以下所有條件時,才應該使用volatile變量:

  • 對變量的寫入操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
  • 該變量沒有包含在具有其他變量的不變式中。
  • 訪問變量不需要加鎖
    通常使用在如下情況:
static class StopTester implements Runnable { 
   private  boolean stop = false;
public void stopMe() {
stop=true;
}
@Override
public void run() {
while(!stop){
//TODO
}
}
}

volatile與synchronized的區別

  • volatile修飾的變量存取時比一般變量消耗的資源要多一點,因為線程有它自己的變量拷貝更為高效。
  • volatile本質是在告訴jvm當前變量在寄存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住.
  • volatile僅能使用在變量級別,synchronized則可以使用在變量,方法.
  • volatile僅能實現變量的修改可見性,但不具備原子特性,而synchronized則可以保證變量的修改可見性和原子性.
  • volatile不會造成線程的阻塞,而synchronized可能會造成線程的阻塞.
  • volatile標記的變量不會被編譯器優化,而synchronized標記的變量可以被編譯器優化.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

  • 轉自:http://www.cnblogs.com/dolphin0520/p/3920373.html vola...
    王帥199207閱讀 465評論 0 0
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,725評論 0 11
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,339評論 11 349
  • 真心相愛的兩個人,不會輸給外貌距離,不會輸給身高年齡,不會輸給前任小三,不會輸給別人的流言蜚語,不會輸給父母的反對...
    酒芯話閱讀 118評論 0 0
  • 誰讓你破涕為笑 首發:沐遙詩雨(MoyleSY) 文:李沐遙 . 有些時光是空的 像在鼓起的窗簾后探頭探腦 偶爾又...
    李沐遙閱讀 457評論 2 4