并發編程系列Synchronized實現原理

      網友投稿 733 2022-05-30

      并發編程系列之Synchronized實現原理

      1、了解synchronized字節碼

      下面給出一個簡單例子,synchronized關鍵字加在兩個方法上,另外一個加在方法里

      public class SynchroinzedDemo { static int a; public static synchronized void add1(int b){ a += b; } public synchronized void add2(int b){ a += b; } public void add3(int b){ synchronized (this){ a += b; } } public static void main(String[] args) { } }

      先用使用javac編譯為class文件,或者在IDE直接運行就行,找到對應class文件,使用如下命令:

      javap -verbose SynchroinzedDemo.class > log.txt

      找到log.txt文件,對比兩個加了synchronized關鍵字的方法,都有ACC_SYNCHRONIZED這個標識

      public static synchronized void add1(int); descriptor: (I)V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field a:I 3: iload_0 4: iadd 5: putstatic #2 // Field a:I 8: return LineNumberTable: line 6: 0 line 7: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 b I public synchronized void add2(int); descriptor: (I)V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=2, args_size=2 0: getstatic #2 // Field a:I 3: iload_1 4: iadd 5: putstatic #2 // Field a:I 8: return LineNumberTable: line 10: 0 line 11: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/example/concurrent/sync/SynchroinzedDemo; 0 9 1 b I

      找到比較關鍵的monitorenter和monitorexit關鍵字,monitorenter和monitorexit關鍵字是什么?后面再介紹

      public void add3(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=2 0: aload_0 1: dup 2: astore_2 3: monitorenter 4: getstatic #2 // Field a:I 7: iload_1 8: iadd 9: putstatic #2 // Field a:I 12: aload_2 13: monitorexit 14: goto 22 17: astore_3 18: aload_2 19: monitorexit 20: aload_3 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any LineNumberTable: line 14: 0 line 15: 4 line 16: 12 line 17: 22 LocalVariableTable: Start Length Slot Name Signature 0 23 0 this Lcom/example/concurrent/sync/SynchroinzedDemo; 0 23 1 b I StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 17 locals = [ class com/example/concurrent/sync/SynchroinzedDemo, int, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4

      綜上:

      方法上加synchronized,是在ACC_SYNCHRONIZED關鍵字

      方法里加synchronized(obj),對應字節碼是monitorenter、monitorexit

      并發編程系列之Synchronized實現原理

      2、Monitor是什么?

      前面的javap編譯,我們知道了monitorenter和monitorexit,synchronized重量級鎖實現依賴于Monitor,所以需要介紹一下說明是Monitor,翻譯過來是監視器?我們知道synchronized鎖的作用和ReentrantLock的作用是一致的,所以synchronized實現同步的原理是否應該一樣?實現上應該也要有互斥量,有等待隊列,有重入計數。

      前面的學習,我們知道synchronized鎖的實現依賴于jvm,要實現鎖就要有互斥量,jvm實現鎖的方式是什么?

      jvm也是程序,因為作為java程序和操作系統的中間件,所以可以直接使用操作系統提供的線程同步原語:mutex互斥量和semaphore信號量,當然也可以使用CAS鎖

      而jvm使用的Monitor又是什么?和jvm以及操作系統底層的線程同步原語又有什么關系?

      Monitor,翻譯過來可以說是官程,或者說是監視器

      在使用操作系統底層的線程同步原語,需要程序員非常小心地控制mutex的down和up操作,否則很容易引起死鎖等問題。為了更容易寫出正確程序,所以在mutex和semaphore的基礎上,提出了更高層次的同步原語monitor,當然monitor并不是操作系統提供的,而是由編譯器,比如java的jvm自己去實現的,所以要使用monitor要確定編程語言是否支持,比如c語言就不支持,java語言支持,因為jvm已經實現了,所以說synchronized是jvm層面的鎖

      jvm如何實現monitor的?可以去github下載openJdk的源碼,路徑:

      openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp penjdk\hotspot\src\share\vm\runtime\objectMonitor.cpp

      主要類是ObjectMonitor.cpp,看了源碼實現:

      // // The ObjectMonitor class is used to implement JavaMonitors which have // transformed from the lightweight structure of the thread stack to a // heavy weight lock due to contention // objectMonitor用于實現javaMonitor,javaMonitor是由于線程爭用而從線程堆棧的輕量級結構轉換為的重量級鎖 // It is also used as RawMonitor by the JVMTI

      // initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, // 等待中的線程數 _recursions = 0; //線程重入次數 _object = NULL; // 存儲該monitor的對象 _owner = NULL; //擁有該monitor的線程 _WaitSet = NULL; //等待線程組成的雙向循環鏈表,_WaitSet 指向第一個節點 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; //多線程競爭鎖進入時的單向鏈表 FreeNext = NULL ; _EntryList = NULL ; //_owner從該雙向循環鏈表中喚醒線程結點,_EntryList是第一個節點 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; // 前一個擁有此監視器的線程ID }

      主要方法:

      bool try_enter (TRAPS) ; // 重量級鎖入口方法 void enter(TRAPS); // monitor 釋放 void exit(bool not_suspended, TRAPS); // 等待方法 void wait(jlong millis, bool interruptable, TRAPS); // 喚醒方法 void notify(TRAPS); void notifyAll(TRAPS);

      synchronized鎖隊列協作流程比較復雜,所以源碼的本博客就不詳細描述,讀者可以參考博客synchronized實現原理 小米信息部技術團隊,里面有對源碼做一個比較清晰的分析

      3、鎖的優化方法

      在jdk6中,java虛擬機團隊對鎖進行了重要的改進,為了優化其性能主要引入了偏向鎖、輕量級鎖、自旋鎖、自適應自旋、鎖消除、鎖粗化等實現。

      為了保證多線程的有效并發,會要求每個線程持有鎖的盡可能短,大部分情況,上面原則是正確的,但是在實際程序運行過程,可能會有一系列操作對一個對象反復加鎖和解鎖,或者加鎖放在循環體中,這種情況會帶來不必要的性能問題,所以jvm會對這種情況進行鎖的粗化處理。鎖粗化就是將鎖的作用范圍限制得盡可能小,只在共享數據的作用域中才進行同步加鎖。

      public void doSomething(int size){ for(int i=0;i

      通過逃逸分析發現其實沒有別的線程產生競爭的可能,別的線程沒有臨界量的引用,虛擬機會直接去掉這個鎖

      StringBuffer是線程安全的,因為這個類使用了很多synchronized鎖,append方法也是

      @Override public synchronized StringBuffer append(Object obj) { toStringCache = null; super.append(String.valueOf(obj)); return this; }

      例子,所以給一個例子,使用append這個方法,不過只是單獨在main里同步調用,因為sBuf變量是本地變量,append方法是同步操作,不會存在競爭(不會逃逸),所以這個程序運行過程jvm可能會進行鎖消除,忽略Stringbuffer里的synchronized鎖

      public static String getStr(String str1, String str2) { StringBuffer sBuf = new StringBuffer(); sBuf.append(str1); sBuf.append(str2); return sBuf.toString(); }

      jdk6引入了偏向鎖來優化無線程爭用時性能,偏向也即偏向獲得它的線程,無鎖化執行。

      當一個線程獲取到鎖后,這把鎖就是偏向鎖。偏向鎖是在對象頭中記錄一個線程ID,當這個線程再次去獲取鎖時,會校驗是否這個線程,如果是直接獲取鎖就可以。

      偏向鎖可以提高帶有同步但是無線程爭用的程序性能,帶有效益權衡性質的優化方法。也就是開啟偏向鎖并不一定都是有利的,如果程序總是存在多個線程競爭的情況,使用偏向鎖反而影響性能,可以使用命令關閉偏向鎖

      -XX:-UseBiasedLocking

      jdk1.6引進了輕量級鎖,輕量級鎖是相對于重量級鎖使用monitor而言的,前面學習,我們知道monitor是基于操作系統底層的線程同步原語。引進輕量級鎖并不是為了替換重量級鎖,而是為了在沒有多線程競爭的前提下,使用輕量級鎖,減少重量級鎖使用操作系統底層互斥量帶來的性能損耗。

      所以輕量級鎖適應條件是同一時間線程爭用不嚴重的情況?!皩τ诮^大部分的鎖,在整個同步周期內都是不存在競爭的”,這是一個經驗,如果滿足,使用輕量級鎖當然可以帶來性能提升,如果存在競爭,則可能比重量級鎖更慢。思考:輕量級鎖使用什么來做互斥量?

      答案是cas鎖,輕量級鎖使用對象頭中的mark work來做互斥判斷

      以上是java對象處于5種不同狀態時,Mark Work中64個位的表現形式,每一行表示對象處于某種狀態時的樣子。其中各部分參數的意義:

      lock:2位的鎖狀態標記位,該標志位的值表示不同的鎖狀態,比如01表示正常無鎖狀態或者偏向鎖狀態,00表示輕量級鎖狀態,10表示重量級鎖狀態。

      biased_lock:biased_lock為1時表示啟動偏向鎖,為0時表示對象沒有啟用偏向鎖,biased_lock和lock配合表示的意義

      age:表示對象的年齡。在jvm的gc中,對象在survivor區復制一次,年齡加1.jvm可以設置一個閾值,默認是15,對象年齡達到15后會進入老年代,可以通過命令-XX:MaxTenuringThreshold進行設置

      identity_hashcode:31位的對象標識hashCode,采用延遲加載技術。調用方法System.identityHashCode()計算,并會將結果寫到該對象頭中。當對象加鎖后(偏向、輕量級、重量級),MarkWord的字節沒有足夠的空間保存hashCode,因此該值會移動到管程Monitor中。

      thread:持有偏向鎖的線程ID

      epoch:偏向鎖的時間戳。

      ptr_to_lock_record:輕量級鎖狀態下,指向棧中鎖記錄的指針。

      ptr_to_heavyweight_monitor:重量級鎖狀態下,指向對象監視器Monitor的指針。

      輕量級鎖的使用過程:

      CAS鎖修改mark word的lock標識為00,成功就獲得鎖,失敗就是有競爭,自旋,自旋獲取不到,轉為重量級鎖

      搶到輕量級鎖后將mark word保存到執行棧上,釋放時CAS還原到對象頭上,能還原成功,意味著沒線程爭用,還原不成功,則表示有線程競爭且阻塞等待了,喚醒等待線程,將mark word 復制給它。

      自旋是一種獲取鎖的機制,并不是鎖的狀態。如果業務場景比較簡單,可以比較快完成,同時又有多個處理器的情況,則搶不到鎖的線程是可以通過循環自旋的方式去獲取鎖。jdk1.4.2引入,默認關閉,jdk1.6改為默認開啟,開關參數:

      -XX:+UseSpinning

      優缺點:如果鎖占用時間很短,自旋等待的效果是不錯的,反之會耗費處理器資源。同時自旋對處理器數量也有要求,必須要有多個處理器。

      自適應自旋:jdk1.6引入了自適應自旋,意味著自旋時間不再固定,而是由前一次在同個鎖上的自旋時間及鎖的持有者狀態來決定的。如果在同一個鎖對象上,自旋等待獲得鎖,并且持有鎖的線程正在運行,那么虛擬機就會認為這次自旋是成功的,進而它允許自旋等待更長的時間。如果很少成功獲取鎖,那在以后去搶鎖時可能省略自旋的過程。有了自適應自旋,虛擬機對鎖的狀況預測會越來越準確。

      4、鎖的升級過程

      鎖的升級過程:無鎖->偏向鎖->輕量級鎖->重量級鎖

      偏向鎖:當一個線程獲取到鎖后,這把鎖就是偏向鎖,偏向鎖是在鎖對象的對象頭中記錄一個線程id,然后該線程再次獲取鎖時,直接獲取就可以

      輕量級鎖:如果有第二個線程來競爭鎖,這時就會升級為輕量級鎖,輕量級鎖是不會阻塞線程的,其底層是通過自旋實現的。自旋是通過CAS獲取一個預期的標識,如果沒獲取到,就會一直循環獲取,獲取到標識,也就標識獲取到鎖

      重量級鎖:如果輕量級鎖一直自旋也獲取不到鎖,才會升級為重量級鎖,重量鎖是會阻塞線程的,也稱之為重鎖

      5、參考資料

      synchronized實現原理 | 小米信息部技術團隊

      《深入理解Java虛擬機》

      https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf

      https://wiki.openjdk.java.net/display/HotSpot/Synchronization

      Java JDK 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:nginx--??圖解及代碼實現正向代理、反向代理及負載均衡(非常實用,建議收藏??)
      下一篇:煤炭行業轉型正當時,華為云助力智慧礦山新發展
      相關文章
      亚洲人成网网址在线看| 亚洲H在线播放在线观看H| 亚洲午夜一区二区电影院| 亚洲综合色自拍一区| 国产亚洲视频在线观看网址| 久久精品国产亚洲AV久| 亚洲成人免费电影| 亚洲精品在线免费观看| 亚洲福利电影在线观看| 亚洲无线一二三四区| 亚洲一级视频在线观看| 亚洲国产精品xo在线观看| 亚洲欧洲日产国码www| 亚洲妓女综合网99| 亚洲一级黄色大片| 亚洲日韩国产二区无码| 亚洲欧美中文日韩视频| 亚洲AV无码国产一区二区三区| 亚洲AV香蕉一区区二区三区| 国产亚洲一卡2卡3卡4卡新区| 日韩国产欧美亚洲v片| 亚洲?V无码成人精品区日韩| 亚洲欧洲久久av| 亚洲伊人久久大香线蕉综合图片| 亚洲色成人网站WWW永久| 亚洲AV无码乱码在线观看裸奔| 亚洲AV午夜福利精品一区二区| 亚洲欧洲日韩不卡| 亚洲色偷偷综合亚洲av78 | 亚洲精品无码中文久久字幕| 亚洲另类无码专区首页| 色偷偷亚洲第一综合网| 国产午夜亚洲精品国产成人小说| 亚洲精品午夜国产VA久久成人| 亚洲AV无码欧洲AV无码网站| 亚洲日本在线观看网址| 亚洲乱码一二三四区麻豆| 亚洲欧洲AV无码专区| 亚洲日韩在线观看| 国精无码欧精品亚洲一区| 91精品国产亚洲爽啪在线影院|