日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Java并發(fā)編程之Synchronized關(guān)鍵字

并發(fā)編程的重點(diǎn)也是難點(diǎn)是數(shù)據(jù)同步、線程安全、鎖。要編寫線程安全的代碼,其核心在于對共享和可變的狀態(tài)的訪問進(jìn)行管理。

共享意味著變量可以由多個(gè)線程訪問,而可變則意味著變量的值在其生命周期內(nèi)可以發(fā)生變化。

當(dāng)多個(gè)線程訪問某個(gè)狀態(tài)變量且其中有一個(gè)線程執(zhí)行寫入操作時(shí),必須采用同步機(jī)制來協(xié)同這些線程對變量的訪問。

Java中的主要同步機(jī)制是關(guān)鍵字synchronized,它提供了一種獨(dú)占的加鎖方式。

勾勾從一下幾個(gè)方面來學(xué)習(xí)synchronized:

關(guān)鍵字synchronized的特性

synchronized關(guān)鍵字可以實(shí)現(xiàn)一個(gè)簡單的策略來防止線程干擾和內(nèi)存一致性錯(cuò)誤,如果一個(gè)對象對多個(gè)線程是可見的,那么該對象的所有讀和寫都需通過同步的方式。

synchronized的特性:

不可中斷:synchronized關(guān)鍵字提供了獨(dú)占的加鎖方式,一旦一個(gè)線程持有了鎖對象,其他線程將進(jìn)入阻塞狀態(tài)或者等待狀態(tài),直到前一個(gè)線程釋放鎖,中間過程不可中斷。

原子性: synchronized關(guān)鍵字的不可中斷性保證了它的原子性。

可見性:synchronized關(guān)鍵字包含了兩個(gè)JVM指令:monitor enter和monitor exit,它能夠保證在任何時(shí)候任何線程執(zhí)行到monitor enter時(shí)都必須從主內(nèi)存中獲取數(shù)據(jù),而不是從線程工作內(nèi)存獲取數(shù)據(jù),在monitor exit之后,工作內(nèi)存被更新后的值必須存入主內(nèi)存,從而保證了數(shù)據(jù)可見性。

有序性:synchronized關(guān)鍵字修改的同步方法是串行執(zhí)行的,但其所修飾的代碼塊中的指令順序還是會發(fā)生改變的,這種改變遵守java happens-before規(guī)則。

可重入性:如果一個(gè)擁有鎖持有權(quán)的線程再次獲取鎖,則monitor的計(jì)數(shù)器會累加1,當(dāng)線程釋放鎖的時(shí)候也會減1,直到計(jì)數(shù)器為0表示線程釋放了鎖的持有權(quán),在計(jì)數(shù)器不為0之前,其他線程都處于阻塞狀態(tài)。

關(guān)鍵字synchronized的用法

synchronized關(guān)鍵字鎖的是對象,修飾的可以是代碼塊和方法,但是不能修飾class對象以及變量。

代碼塊,鎖對象即是object

 
 
 
 
  1. private final Object obj = new Object(); 
  2. public void sync(){ 
  3.         synchronized (obj){  
  4.                     
  5.         }         
  6.    } 

 方法,鎖對象即是this

 
 
 
 
  1. public synchronized void syncMethod(){ 
  2.  
  3.  } 

 靜態(tài)方法,鎖對象既是class

 
 
 
 
  1. public synchronized static void syncStaticMethod(){ 
  2.  
  3.  } 

 勾勾在開發(fā)中最常用的是用synchronized關(guān)鍵字修飾對象,可以控制鎖的粒度,所以針對最常用的場景勾勾去了解了它的字節(jié)碼文件,先來看看勾勾的測試用例:

 
 
 
 
  1. public class TestSynchronized { 
  2.     private int index; 
  3.     private final static int MAX = 100; 
  4.     public void sync(){        
  5.         synchronized (new Object()){                 
  6.             while (index < MAX){                         
  7.                 index ++; 
  8.             } 
  9.         } 
  10.     } 

 運(yùn)行命令 “javac -encoding UTF-8 TestSynchronized.java”編輯成class文件,然后

運(yùn)行命令“javap -c TestSynchronized.class”得到字節(jié)碼文件:

 
 
 
 
  1. public com.example.demo.articles.thread.TestSynchronized();  
  2.    Code: 
  3.       0: aload_0 
  4.       1: invokespecial #1                  // Method java/lang/Object."":()V 
  5.       4: return 
  6.  
  7.  public void sync(); 
  8.    Code: 
  9.       0: new           #2                  // class java/lang/Object 
  10.       3: dup 
  11.       4: invokespecial #1                  // Method java/lang/Object."":()V 
  12.       7: dup 
  13.       8: astore_1 
  14.       9: monitorenter  //進(jìn)入同步代碼塊 
  15.      10: aload_0       //加載數(shù)據(jù) 
  16.      11: getfield      #3                  // Field index:I 
  17.      14: bipush        100 
  18.      16: if_icmpge     32 
  19.      19: aload_0 
  20.      20: dup 
  21.      21: getfield      #3                  // Field index:I 
  22.      24: iconst_1 
  23.      25: iadd          // 加1操作 
  24.      26: putfield      #3                  // Field index:I 
  25.      29: goto          10 //跳轉(zhuǎn)至10行 
  26.      32: aload_1       
  27.      33: monitorexit  // 退出同步代碼塊 
  28.      34: goto          42 //跳轉(zhuǎn)至42行 
  29.      37: astore_2     // 刷新數(shù)據(jù) 
  30.      38: aload_1 
  31.      39: monitorexit   
  32.      40: aload_2 
  33.      41: athrow 
  34.      42: return 
  35.    Exception table: 
  36.       from    to  target type 
  37.          10    34    37   any 
  38.          37    40    37   any 

 monitorenter和monitorexit是成對出現(xiàn)的,有時(shí)候你看到的是一個(gè)monitorenter對應(yīng)多個(gè)monitorexit,但是能肯定的一定點(diǎn)是每一個(gè)monitorexit之前必有一個(gè)monitorenter。

從字節(jié)碼文件中可以看到monitorenter之后執(zhí)行了aload操作,monitorexit之后執(zhí)行了astore操作。

TIPS:在使用synchronized關(guān)鍵字時(shí)注意事項(xiàng)

  1. 鎖的對象不能為空;
  2. 鎖的范圍不宜太大;
  3. 不要試圖使用不同的monitor來鎖同一個(gè)方法;
  4. 避免多個(gè)鎖交叉等待導(dǎo)致死鎖;

鎖膨脹

在jdk1.6之前,線程在獲取鎖時(shí),如果鎖對象已經(jīng)被其他線程持有,此線程將掛起進(jìn)入阻塞狀態(tài),喚醒阻塞線程的過程涉及到了用戶態(tài)和內(nèi)核態(tài)的切換,性能損耗比較大。

synchronized作為親兒子,混的太差肯定不行,在jdk1.6對其進(jìn)行了優(yōu)化,將鎖狀態(tài)分為了無鎖狀態(tài),偏向鎖,輕量級鎖,重量級鎖。

鎖的升級過程既是:

在了解鎖的升級過程之前,勾勾重點(diǎn)理解了monitor和對象頭。

在第一次研究鎖膨脹的時(shí)候因?yàn)闆]有花時(shí)間去理解這兩個(gè)概念,勾勾對鎖升級的記憶只持續(xù)了3天,最后勾勾又用了兩天的時(shí)間去學(xué)習(xí)對象頭和monitor,才算是真正的理解鎖的膨脹原理。所以大家在學(xué)習(xí)一個(gè)知識的時(shí)候,不要靠背去記憶一個(gè)知識點(diǎn),一定要知其然。

每一個(gè)對象都與一個(gè)monitor相關(guān)聯(lián),monitor對象與實(shí)例對象一同創(chuàng)建并銷毀,monitor是C++支持的一個(gè)監(jiān)視器。鎖對象的爭奪既是爭奪monitor的持有權(quán)。

勾勾在OpenJdk源碼中找到了ObjectMonitor的源碼:

 
 
 
 
  1.  // initialize the monitor, exception the semaphore, all other fields 
  2.   //  are simple integers or pointers     
  3.   ObjectMonitor() {   
  4.     _header       = NULL; 
  5.     _count        = 0; 
  6.     _waiters      = 0, 
  7.     _recursions   = 0; 
  8.     _object       = NULL; 
  9.     _owner        = NULL; 
  10.     _WaitSet      = NULL; 
  11.     _WaitSetLock  = 0 ; 
  12.     _Responsible  = NULL ; 
  13.     _succ         = NULL ; 
  14.     _cxq          = NULL ; 
  15.     FreeNext      = NULL ; 
  16.     _EntryList    = NULL ; 
  17.     _SpinFreq     = 0 ; 
  18.     _SpinClock    = 0 ; 
  19.     OwnerIsThread = 0 ; 
  20.   } 
  21.  protected:                         // protected for jvmtiRawMonitor 
  22.   void *  volatile _owner;          // pointer to owning thread OR BasicLock 
  23.   volatile intptr_t  _recursions;   // recursion count, 0 for first entry 
  24.  private: 
  25.   int OwnerIsThread ;               // _owner is (Thread *) vs SP/BasicLock 
  26.   ObjectWaiter * volatile _cxq ;    // LL of recently-arrived threads blocked on entry. 
  27.                                     // The list is actually composed of WaitNodes, acting 
  28.                                     // as proxies for Threads. 
  29.  protected: 
  30.   ObjectWaiter * volatile _EntryList ;     // Threads blocked on entry or reentry. 
  31.  private: 
  32.   Thread * volatile _succ ;          // Heir presumptive thread - used for futile wakeup throttling 
  33.   Thread * volatile _Responsible ; 
  34.   int _PromptDrain ;                // rqst to drain cxq into EntryList ASAP 

 owner:指向線程的指針。即鎖對象關(guān)聯(lián)的monitor中的owner指向了哪個(gè)線程表示此線程持有了鎖對象。

waitSet:進(jìn)入阻塞等待的線程隊(duì)列。當(dāng)線程調(diào)用wait方法之后,就會進(jìn)入waitset隊(duì)列,可以等待其他線程喚醒。

entryList:當(dāng)多個(gè)線程進(jìn)入同步代碼塊之后,處于阻塞狀態(tài)的線程就會被放入entryList中。

那什么是對象頭呢,它與synchronized又有什么關(guān)系呢?

在JVM中,對象在內(nèi)存中分為3塊區(qū)域:

  • 對象頭Mark Word(標(biāo)記字段):用于存儲對象的hashcode,分代年齡,鎖標(biāo)志位,是否可偏向標(biāo)志,在運(yùn)行期間,其存儲的數(shù)據(jù)會發(fā)生變化。Klass Point(類型指針):該指針指向它的類元數(shù)據(jù),JVM通過這個(gè)指針確定對象是哪個(gè)類的實(shí)例。該指針的位長度為JVM的一個(gè)字大小,即32位的JVM為32位,64位的JVM為64位。
  • 實(shí)例數(shù)據(jù)用于存放類的數(shù)據(jù)信息
  • 填充數(shù)據(jù)虛擬機(jī)要求對象起始地址必須是8字節(jié)的整數(shù)倍,當(dāng)不滿足時(shí)需對其填充。

我們先通過一張圖了解下在鎖升級的過程中對象頭的變化:

接下來我們分析鎖升級的過程:

第一個(gè)分支鎖標(biāo)志為01:

當(dāng)線程運(yùn)行到同步代碼塊時(shí),首先會判斷鎖標(biāo)志位,如果鎖標(biāo)志位為01,則繼續(xù)判斷偏向標(biāo)志。

如果偏向標(biāo)志為0,則表示鎖對象未被其他線程持有,可以獲取鎖。此時(shí)當(dāng)前線程通過CAS的方法修改線程ID,如果修改成功,此時(shí)鎖升級為偏向鎖。

如果偏向標(biāo)志為1,則表示鎖對象已經(jīng)被占有。

進(jìn)一步判斷線程id是否相等,相等則表示當(dāng)前線程持有的鎖對象,可以重入。

如果線程id不相等,則表示鎖被其他線程占有。

需進(jìn)一步判斷持有偏向鎖的線程的活動狀態(tài),如果原持有偏向鎖線程已經(jīng)不活動或者已經(jīng)退出同步代碼塊,則表示原持有偏向鎖的線程可以釋放偏向鎖。釋放后偏向鎖回到無鎖狀態(tài),線程再次嘗試獲取鎖。主要是因?yàn)槠蜴i不會主動釋放,只有其他線程競爭偏向鎖的時(shí)候才會釋放。

如果原持有偏向鎖的線程沒有退出同步代碼塊,則鎖升級為輕量級鎖。

偏向鎖的流程圖如下:

第二個(gè)分支鎖標(biāo)志為00:

在第一個(gè)分支中我們了解到在如果偏向鎖已經(jīng)被其他線程占有,則鎖會被升級為輕量級鎖。

此時(shí)原持有偏向鎖的線程的棧幀中分配鎖記錄Lock Record,將對象頭中的Mark Word信息拷貝到鎖記錄中,Mark Word的指針指向了原持有偏向鎖線程中的鎖記錄,此時(shí)原持有偏向鎖的線程獲取輕量級鎖,繼續(xù)執(zhí)行同步塊代碼。

如果線程在運(yùn)行同步塊時(shí)發(fā)現(xiàn)鎖的標(biāo)志位為00,則在當(dāng)前線程的棧幀中分配鎖記錄,拷貝對象頭中的Mark Word到鎖記錄中。通過CAS操作將Mark Word中的指針指向自己的鎖記錄,如果成功,則當(dāng)前線程獲取輕量鎖。

如果修改失敗,則進(jìn)入自旋,不斷通過CAS的方式修改Mark Word中的指針指向自己的鎖記錄。

當(dāng)自旋超過一定次數(shù)(默認(rèn)10次),則升級為重量鎖。

輕量鎖的鎖是主動釋放的,持有輕量鎖的線程在執(zhí)行完同步代碼塊后,會先判斷Mark Word中的指針是否依然指向自己,且自己鎖記錄中的Mark Word信息與鎖對象的Mark Word信息一致,如果都一致,則釋放鎖成功。

如果不一致,則鎖有可能已經(jīng)被升級為重量鎖。

輕量級流程圖如下圖:

第三個(gè)分支鎖標(biāo)志位為10:

鎖標(biāo)志為10時(shí),此時(shí)鎖已經(jīng)為重量鎖,線程會先判斷monitor中的owner指針指向是否為自己,是則獲取重量鎖,不是則會掛起。

整個(gè)鎖升級過程中的流程圖如下,如果看懂了一定要自己畫一遍。

總結(jié):

synchronized關(guān)鍵字是一種獨(dú)占的加鎖方式,不可中斷,保證了原子性,可見性,和有序性。

synchronized關(guān)鍵字可用于修飾方法和代碼塊,不能用于修飾變量和類。

多線程在執(zhí)行同步代碼塊時(shí)獲取鎖的過程在不同的鎖狀態(tài)下不一樣,偏向鎖是修改Mark Word中的線程ID,輕量鎖是修改Mark Word的指針指向自己的鎖記錄,重量鎖是修改monitor中的指針指向自己。

今天就學(xué)到這里了!收工!

并發(fā)編程、JVM、數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)知識更新完了,后續(xù)還會慢慢補(bǔ)充!


當(dāng)前標(biāo)題:Java并發(fā)編程之Synchronized關(guān)鍵字
標(biāo)題來源:http://www.5511xx.com/article/ccsegdh.html