成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java并發編程之Synchronized關鍵字

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

 [[386719]]

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

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

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

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

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


關鍵字synchronized的特性

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

synchronized的特性:

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

原子性: synchronized關鍵字的不可中斷性保證了它的原子性。

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

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

可重入性:如果一個擁有鎖持有權的線程再次獲取鎖,則monitor的計數器會累加1,當線程釋放鎖的時候也會減1,直到計數器為0表示線程釋放了鎖的持有權,在計數器不為0之前,其他線程都處于阻塞狀態。

關鍵字synchronized的用法

synchronized關鍵字鎖的是對象,修飾的可以是代碼塊和方法,但是不能修飾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.  } 

 靜態方法,鎖對象既是class

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

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

  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”得到字節碼文件:

  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       //加載數據 
  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 //跳轉至10行 
  26.      32: aload_1       
  27.      33: monitorexit  // 退出同步代碼塊 
  28.      34: goto          42 //跳轉至42行 
  29.      37: astore_2     // 刷新數據 
  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是成對出現的,有時候你看到的是一個monitorenter對應多個monitorexit,但是能肯定的一定點是每一個monitorexit之前必有一個monitorenter。

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

TIPS:在使用synchronized關鍵字時注意事項

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

鎖膨脹

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

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

鎖的升級過程既是:


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

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

每一個對象都與一個monitor相關聯,monitor對象與實例對象一同創建并銷毀,monitor是C++支持的一個監視器。鎖對象的爭奪既是爭奪monitor的持有權。

勾勾在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:指向線程的指針。即鎖對象關聯的monitor中的owner指向了哪個線程表示此線程持有了鎖對象。

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

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

那什么是對象頭呢,它與synchronized又有什么關系呢?

在JVM中,對象在內存中分為3塊區域:

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

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


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

第一個分支鎖標志為01:

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

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

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

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

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

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

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

偏向鎖的流程圖如下:


第二個分支鎖標志為00:

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

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

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

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

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

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

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

輕量級流程圖如下圖:


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

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

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

總結:

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

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

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

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

并發編程、JVM、數據結構基礎知識更新完了,后續還會慢慢補充!

 

責任編輯:姜華 來源: 今日頭條
相關推薦

2017-05-27 20:59:30

Java多線程synchronize

2025-06-13 08:00:00

Java并發編程volatile

2019-12-20 15:19:41

Synchroinze線程安全

2024-03-15 15:12:27

關鍵字底層代碼

2024-11-20 15:55:57

線程Java開發

2020-11-13 08:42:24

Synchronize

2023-06-26 08:02:34

JSR重排序volatile

2022-01-26 00:03:00

關鍵字線程JVM

2009-08-12 13:37:01

Java synchr

2021-01-12 09:22:18

Synchronize線程開發技術

2021-08-15 08:11:54

AndroidSynchronize關鍵字

2016-09-19 21:53:30

Java并發編程解析volatile

2021-01-05 10:26:50

鴻蒙Javafinal

2009-06-29 18:26:11

Java多線程Synchronize同步類

2017-09-19 14:53:37

Java并發編程并發代碼設計

2012-03-01 12:50:03

Java

2023-05-15 09:39:10

Java監視器鎖

2025-03-26 00:55:00

2025-01-09 10:30:40

2025-03-20 06:48:55

性能優化JDK
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲高清三级 | 一级片免费网站 | 国产乱码久久久久久 | 午夜小电影 | 91综合在线视频 | 天天影视网天天综合色在线播放 | 久久出精品| 久色一区| 日日夜夜视频 | 三级成人在线 | 精品国产一区二区国模嫣然 | 黄色大片视频 | 成人国产午夜在线观看 | 亚洲人成人一区二区在线观看 | 欧美色综合一区二区三区 | 在线视频成人 | 福利影院在线看 | 不卡一区二区三区四区 | 岛国精品| 欧美一区二区三区精品 | 国产免费一区二区三区最新6 | 国产午夜精品视频 | 日本一区二区高清不卡 | 久久久久av | 夜夜精品浪潮av一区二区三区 | 夜夜爽99久久国产综合精品女不卡 | 亚洲色欲色欲www | 国产亚洲一区精品 | 国产成人精品网站 | 国产精品成人在线播放 | 涩涩视频网站在线观看 | 国产目拍亚洲精品99久久精品 | 蜜桃视频在线观看免费视频网站www | 色必久久| 亚洲欧美在线一区 | 欧洲av在线 | 欧美一区二区免费 | 国产在线视频网 | 欧美一区二区三区在线观看 | 欧美日韩一区二区在线 | 欧美日韩亚洲二区 |