自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java并發(fā)編程之Synchronized關(guān)鍵字

開發(fā) 后端
并發(fā)編程的重點也是難點是數(shù)據(jù)同步、線程安全、鎖。要編寫線程安全的代碼,其核心在于對共享和可變的狀態(tài)的訪問進行管理。Java中的主要同步機制是關(guān)鍵字synchronized,它提供了一種獨占的加鎖方式。

 [[386719]]

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

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

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

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

勾勾從一下幾個方面來學習synchronized:


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

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

synchronized的特性:

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

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

可見性:synchronized關(guān)鍵字包含了兩個JVM指令:monitor enter和monitor exit,它能夠保證在任何時候任何線程執(zhí)行到monitor enter時都必須從主內(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ī)則。

可重入性:如果一個擁有鎖持有權(quán)的線程再次獲取鎖,則monitor的計數(shù)器會累加1,當線程釋放鎖的時候也會減1,直到計數(shù)器為0表示線程釋放了鎖的持有權(quán),在計數(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.     } 

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

運行命令“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."<init>":()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."<init>":()V 
  12.       7: dup 
  13.       8: astore_1 
  14.       9: monitorenter  //進入同步代碼塊 
  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)的,有時候你看到的是一個monitorenter對應多個monitorexit,但是能肯定的一定點是每一個monitorexit之前必有一個monitorenter。

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

TIPS:在使用synchronized關(guān)鍵字時注意事項

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

鎖膨脹

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

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

鎖的升級過程既是:


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

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

每一個對象都與一個monitor相關(guān)聯(lián),monitor對象與實例對象一同創(chuàng)建并銷毀,monitor是C++支持的一個監(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指向了哪個線程表示此線程持有了鎖對象。

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

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

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

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

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

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


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

第一個分支鎖標志為01:

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

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

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

進一步判斷線程id是否相等,相等則表示當前線程持有的鎖對象,可以重入。

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

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

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

偏向鎖的流程圖如下:


第二個分支鎖標志為00:

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

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

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

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

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

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

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

輕量級流程圖如下圖:


第三個分支鎖標志位為10:

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

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

總結(jié):

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

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

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

今天就學到這里了!收工!

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

 

責任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2017-05-27 20:59:30

Java多線程synchronize

2019-12-20 15:19:41

Synchroinze線程安全

2024-03-15 15:12:27

關(guān)鍵字底層代碼

2024-11-20 15:55:57

線程Java開發(fā)

2020-11-13 08:42:24

Synchronize

2023-06-26 08:02:34

JSR重排序volatile

2022-01-26 00:03:00

關(guān)鍵字線程JVM

2009-08-12 13:37:01

Java synchr

2021-01-12 09:22:18

Synchronize線程開發(fā)技術(shù)

2021-08-15 08:11:54

AndroidSynchronize關(guān)鍵字

2016-09-19 21:53:30

Java并發(fā)編程解析volatile

2021-01-05 10:26:50

鴻蒙Javafinal

2009-06-29 18:26:11

Java多線程Synchronize同步類

2017-09-19 14:53:37

Java并發(fā)編程并發(fā)代碼設計

2012-03-01 12:50:03

Java

2023-05-15 09:39:10

Java監(jiān)視器鎖

2025-03-20 06:48:55

性能優(yōu)化JDK

2025-03-26 00:55:00

2025-01-09 10:30:40

2012-03-09 10:44:11

Java
點贊
收藏

51CTO技術(shù)棧公眾號