synchronized 底層如何實現?什么是鎖升級、降級?

synchronized 底層如何實現?什么是鎖升級、降級?

synchronized 代碼塊是由一對 monitorenter/monitorexit 指令實現的,Monitor 對象是同步的基本實現單元。

  • https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#d5e13622

在Java6之前, Monitor的實現完全是依靠操作系統內部的互斥鎖,因為需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作。現代的( Oracle)JDK中,JVM對此進行了大刀闊斧地改進,提供了三種不同的 Monitor 實現,也就是常說的三種不同的鎖:偏斜鎖( Biased Locking)輕量級鎖重量級鎖,大大改進了其性能。

什么是鎖升級,降級?

所謂的鎖升級、降級,就是 JVM 優化 synchronized 運行的機制,當 JVM 監測到不同的競爭狀況是,會自動切換到不同的鎖實現。這種切換就是鎖的升級、降級。

對象的結構

說偏向鎖之前,需要理解對象的結構,對象由多部分構成的,對象頭,屬性字段、補齊區域等。所謂補齊區域是指如果對象總大小不是4字節的整數倍,會填充上一段內存地址使之成為整數倍。

偏向鎖又和對象頭密切相關,對象頭這部分在對象的最前端,包含兩部分或者三部分:Mark Words、Klass Words,如果對象是一個數組,那么還可能包含第三部分:數組的長度。

  • Klass Word里面存的是一個地址,占32位或64位,是一個指向當前對象所屬于的類的地址,可以通過這個地址獲取到它的元數據信息。
  • Mark Word需要重點說一下,這里面主要包含對象的哈希值、年齡分代、hashcode、鎖標志位等。

如果應用的對象過多,使用64位的指針將浪費大量內存。64位的JVM比32位的JVM多耗費50%的內存。 我們現在使用的64位 JVM會默認使用選項 +UseCompressedOops 開啟指針壓縮,將指針壓縮至32位。

以64位操作系統為例,對象頭存儲內容圖例

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  無鎖
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向鎖
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  輕量鎖
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量鎖
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

對象頭中的信息如何理解呢,舉個例子

從該對象頭中分析加鎖信息,MarkWordk為0x0000700009b96910,二進制為0xb00000000 00000000 01111111 11110000 11001000 00000000 01010011 11101010。 倒數第三位為"0",說明不是偏向鎖狀態,倒數兩位為"10",因此,是重量級鎖狀態,那么前面62位就是指向互斥量的指針。

basied_locklock狀態001無鎖101偏向鎖000輕量級鎖010重量級鎖011GC標記

  • age:Java GC標記位對象年齡。
  • identity_hashcode:對象標識Hash碼,采用延遲加載技術。當對象使用HashCode()計算后,并會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到線程Monitor中。
  • thread:持有偏向鎖的線程ID和其他信息。這個線程ID并不是JVM分配的線程ID號,和Java Thread中的ID是兩個概念。
  • epoch:偏向時間戳。
  • ptr_to_lock_record:指向棧中鎖記錄的指針。
  • ptr_to_heavyweight_monitor:指向線程Monitor的指針。

無鎖

A a = new A();System.out.println(ClassLayout.parseInstance(a).toPrintable());

可以看到最后 00000001 basied_lock = 0, lock =01 表示無鎖

JavaThread.synchronizestructure.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

偏斜鎖

當沒有競爭出現時,默認使用偏斜鎖。 JVM 會利用 CAS 操作在對象頭上的 Mark Word 部分設置線程 ID ,以表示對象偏向當前線程。所以并不涉及真正的互斥鎖,這樣做的假設是基于在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無竟爭開銷。 測試代碼:

Thread.sleep(5000);
A a = new A();
synchronized (a) {System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

運行結果:

JavaThread.synchronizestructure.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           05 28 8d 02 (00000101 00101000 10001101 00000010) (42805253)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101 中 basied_lock = 1, lock =01 表示偏斜鎖

輕量級鎖

thread1中依舊輸出偏向鎖,主線程獲取對象A時,thread1雖然已經退出同步代碼塊,但主線程和thread1仍然為鎖的交替競爭關系。故此時主線程輸出結果為輕量級鎖。 測試代碼:

Thread.sleep(5000);
A a = new A();Thread thread1= new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread1 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向鎖}}
};
thread1.start();
thread1.join();
Thread.sleep(10000);synchronized (a){System.out.println("main locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖
}
}

運行結果:

JavaThread.synchronizestructure.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           05 40 e9 19 (00000101 01000000 11101001 00011001) (434716677)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes totalmain locking
JavaThread.synchronizestructure.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           38 f6 c7 02 (00111000 11110110 11000111 00000010) (46659128)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101 依然是偏向鎖,00111000 是輕量級鎖

重量級鎖

thread1 和 thread2?同時競爭對象a,此時輸出結果為重量級鎖

測試代碼:

Thread.sleep(5000);
A a = new A();
Thread thread1 = new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread1 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());try {//讓線程晚點兒死亡,造成鎖的競爭Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
};
Thread thread2 = new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread2 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
};
thread1.start();
thread2.start();

運行結果:

thread2 locking
JavaThread.synchronizestructure.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           7a f5 99 17 (01111010 11110101 10011001 00010111) (395965818)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           62 c1 00 20 (01100010 11000001 00000000 00100000) (536920418)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

01111010 basied_lock = 0 lock=10 重量級鎖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386480.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386480.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386480.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring主要用到兩種設計模式

Spring主要用到兩種設計模式 1、工廠模式 Spring容器就是實例化和管理全部Bean的工廠。 工廠模式可以將Java對象的調用者從被調用者的實現邏輯中分離出來。 調用者只關心被調用者必須滿足的某種規則,這里的規則我們可以看做是接口,而不必關心實例的具體實…

意外收獲字節跳動內部資料,已開源

前言 每年的3、4月份是各大企業為明年拓展業務大量吸納人才的關鍵時期,招聘需求集中、空缺崗位多,用人單位也習慣在初秋進行大規模招聘。 金九銀十,招聘旺季,也是一個求職旺季。 不打無準備的仗,在這種關鍵時期&…

OpenJDK研究

這里以32位Windows 7為例 安裝必須的軟件 JDK1.8CygwinMicrosoft Visual Studio 2010 (請下載英文版) 這里就不介紹怎么安裝這些軟件了,假設安裝后的目錄名分別是: (請根據你的實際情況調整這些目錄名) D:\JavaSE1.8 D:\Cygwin D:\VS2010 增加環境變…

獵頭文章(一)

(一)從來沒有想過自己會加入這一行, 從開始自己喜歡的專業通訊, 到后來喜歡的管理,,幻想過是專業高手, 幻想過管理專家, 卻從來沒有想過進入這一行,但 真的在我剛剛離開校…

成功跳槽百度工資從15K漲到28K,威力加強版

前言 看到一篇文章中提到“最近幾年國內的初級Android程序員已經很多了,但是中高級的Android技術人才仍然稀缺“,這的確不假,從我在百度所進行的一些面試來看,找一個適合的高級Android工程師的確不容易,一般需要進行大…

Redis下載及安裝(windows版)

下載地址 1、Github下載地址:https://github.com/MicrosoftArchive/redis/releases 2、百度網盤下載地址 https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g 密碼:kdfq 安裝過程 1.首先先把下載的壓縮包解壓到一個文件夾中 2.打開cmd指令窗口 3.輸入你剛…

成功跳槽百度工資從15K漲到28K,跳槽薪資翻倍

前言 這篇文章主要是分享今年上半年的面試心得,現已就職于某大廠有三個月了,近期有很多公司均已啟動秋招,也祝大家在 2020 的下半年面試順利,獲得理想的offer! 之前找工作的那段時間感想頗多,總結一點面試…

分布式鎖RedLock的java實現Redisson

1. 概述Redisson是一個在Redis的基礎上實現的Java駐內存數據網格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue,…

我三年開發經驗,從字節跳動抖音離職后,看看這篇文章吧!

最新BAT大廠面試者整理的Android面試題目! 近期根據網友分享大廠面試題目,今天我將網友面試的BAT等大廠Android面試題目整理出來,希望能夠幫助大家! 珍藏版(1)——高級 UI 晉升 第一節、觸摸事件分發機制…

arthas命令redefine實現Java熱更新

Arthas非常重要的命令redefine,主要作用是加載外部的.class文件,用來替換JVM已經加載的類,總結起來就是實現了Java的熱更新。 redefine在一下幾種情況中會失敗:1、增加了field;2、增加了method;3、替換正在…

安裝 ZendServer-CE 可能遇到的問題

安裝后,打開http://localhost:10081/ZendServer 出現如下頁面. Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. Please contact the server administrator, adminexample.com and inform them…

Elk7.2 Docker

正如官方所說的那樣 https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html,Elasticsearch默認使用mmapfs目錄來存儲索引。操作系統默認的mmap計數太低可能導致內存不足,我們可以使用下面這條命令來增加內存 為了防止…

我了解到的面試的一些小內幕!附面試題答案

背景 首先我是個菜雞,工資也低的一筆。 剛畢業時候在一家國企上班干 app 開發,干了快兩年的時候,跳槽到了一家偽大廠干安全。投了不少簡歷都沒有回音,只有這加偽大廠要我就來了。當時說好了會接觸一些底層的東西,然而…

學習單調隊列小結

因為一直在聽身邊的人說什么單調隊列/斜率優化dp/背包,(ps:我也不清楚這樣稱呼對不對,因為我真心是沒見過這些東西)我都覺得那是神一樣的東西。終于抽出時間學了一下。 昨天在朋友一本書里面看到一句話,這里先跟大家分享一下: 沒有…

@Async join

直接貼代碼 自定義線程池 package com.xh.lawsuit.rest.modular.example; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecu…

我們究竟還要學習哪些Android知識?完整版開放下載

前言 移動研發火熱不停,越來越多人開始學習 android 開發。但很多人感覺入門容易成長很難,對未來比較迷茫,不知道自己技能該怎么提升,到達下一階段需要補充哪些內容。市面上也多是談論知識圖譜,缺少體系和成長節奏感&a…

ELK7.8.1的Docker搭建過程

在linux下首先在目錄準備文件 首先說明,我的電腦宿主機的IP是192.168.1.5 為es準備文件 mkdir -p /opt/elk7/es cd /opt/elk7/es #創建對應的文件夾 數據 / 日志 / 配置 mkdir conf data logs #授權 chmod 777 -R conf data logs然后進入到/opt/elk7/es/conf下 …

如何使用git創建項目,創建分支

git config -global user.name "Your name" git config -global user.email "youexample.com" 建立一個存放工程的文件夾 git init命令用于初始化當前所在目錄的這個項目 會創建一個隱藏文件 .git 創建 main.c 文件 創建 .gitignore文件,忽略…

我們究竟還要學習哪些Android知識?附贈課程+題庫

2021新的一年,開啟新的征程,回顧2020,真是太“南”了。 從年初各大廠裁員,竟然成為一件理所應當的事情,到四月份 GitHub 上“996.ICU” 引起了大家的共鳴。即使我們兢兢業業“996”,但依舊難以抵御 35 歲時…

WINDOWS上KAFKA運行環境安裝

WINDOWS上KAFKA運行環境安裝 1. 安裝JDK 1.1 安裝文件:http://www.oracle.com/technetwork/java/javase/downloads/index.html 下載JDK 1.2 安裝完成后需要添加以下的環境變量(右鍵點擊“我的電腦” -> "高級系統設置" -> "環境變…