synchronized關(guān)鍵字的原理刨析
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
66套java從入門到精通實(shí)戰(zhàn)課程分享
前言
關(guān)于synchronized原理解析,我們首先要分析一下對(duì)象都有哪些東西,對(duì)象頭到底存儲(chǔ)了什么,synchronzied關(guān)鍵字到底是如何進(jìn)行鎖膨脹的,在使用過程中同步方法塊和同步代碼塊到底有什么區(qū)別,在回頭看synchronized的使用。針對(duì)的時(shí)JDK1.6之后的版本的synchronized深度分析。
前期準(zhǔn)備
為輸出對(duì)象頭導(dǎo)入jar
<dependency>
????<groupId>org.openjdk.jolgroupId>
????<artifactId>jol-coreartifactId>
????<version>0.11version>
dependency>設(shè)置偏向鎖的啟動(dòng)延遲
#關(guān)閉偏向鎖(為什么要關(guān)閉偏向鎖,有什么好處嘛,這個(gè)問題先不做回答)
-XX:-UseBiasedLocking
#設(shè)置偏向鎖的一個(gè)啟動(dòng)延遲
-XX:BiasedLockingStartupDelay=0對(duì)象
對(duì)象中有哪些部分
新建一個(gè)對(duì)象,進(jìn)行main方法輸出
public?class?A?{
???int?status=0;
???boolean?flag=true;
}public?static?void?main(String[] args) {
????a=new?A(); System.out.println(ClassLayout.parseInstance(a).toPrintable());
}輸出的一個(gè)對(duì)象信息(當(dāng)前的操作系統(tǒng)是一個(gè)64位的,32位的是不一樣的)

從這個(gè)上面看,我們可以分析出包含96bit(12byte*8)的對(duì)象頭和兩個(gè)屬性(status,flag)字段屬性。這些都屬于對(duì)象數(shù)據(jù),int 類型的數(shù)據(jù)我們知道,占用4個(gè)byte,1個(gè)boolean值得可以使用1個(gè)byte去表示,那么為什么后面還出現(xiàn)了7個(gè)byte,這是因?yàn)閖vm在分配內(nèi)存的時(shí)候只能是8的一個(gè)倍數(shù)。所以就出現(xiàn)了7個(gè)byte。
所以我們小小的總結(jié)一下。
一個(gè)對(duì)象中包含對(duì)象頭,實(shí)列數(shù)據(jù),對(duì)象填充.

對(duì)象頭有哪些東西
通過文檔查找來驗(yàn)證
jvm底層一個(gè)是基于C/C++來實(shí)現(xiàn)的,查看版本java -version 可以知道jvm的實(shí)現(xiàn)是HotSport來實(shí)現(xiàn)的jvm規(guī)范和標(biāo)準(zhǔn)的。通過openjdk的官方文檔 [https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html]查看對(duì)于對(duì)象頭的一個(gè)定義。

說了一下包含兩部分,mark word 和 klass pointer(這個(gè)對(duì)象對(duì)應(yīng)的class類指針地址) 兩部分。在open Jdk的源碼中有一個(gè)markOop的文件中說明了在64bit的操作系統(tǒng)中,mark word 占用了64位的。所以我們得出一個(gè)結(jié)論。

那么此時(shí)我們知道了,對(duì)象頭中包含了 這個(gè)對(duì)象對(duì)應(yīng)的class類的類型,Gc age,hashcode , synchronized關(guān)鍵字的同步狀態(tài)。
對(duì)象頭再深入分析

這里的klass word 為什么是32bit呢,因?yàn)樵趈vm中如果開啟了指針壓縮(1.8默認(rèn)開啟的)就會(huì)對(duì)對(duì)象頭進(jìn)行壓縮。所以看到是32bit,如果關(guān)閉就會(huì)看到是64bit。
關(guān)閉指針壓縮
-XX:-UseCompressedOops鎖的一個(gè)狀態(tài)分析
new 對(duì)象A
public?static?void?main(String[] args) {
???a=new?A();
???System.out.println(ClassLayout.parseInstance(a).toPrintable());
???a.hashCode();
???System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
第一次打印這個(gè)對(duì)象A的情況(沒有計(jì)算a的hashcode)無鎖可偏向,就是沒有線程來獲取這把鎖,也沒有計(jì)算hashcode。

全部為0的就是沒有HashCode 或者 線程ID 是空的。
第二次打印這個(gè)對(duì)象A的情況(計(jì)算了a的hashcode),無鎖不可偏向,沒有線程來獲取這把鎖,但是計(jì)算了hashcode。
并設(shè)置了HashCode 到mark word 中。
當(dāng)我們的代碼變成這樣下面這樣時(shí),我們?cè)诳催@個(gè)對(duì)象頭的中mark word 的一個(gè)鎖的狀態(tài)。
public?static?void?main(String[] args) {
???a=new?A();
???System.out.println(ClassLayout.parseInstance(a).toPrintable());
???a.hashCode();
???System.out.println(ClassLayout.parseInstance(a).toPrintable());
???lock();
???new?Thread(()->{
??????lock();
???}).start();
}
public??static??void??lock(){
???synchronized (a){
??????System.out.println("線程名稱=="+Thread.currentThread().getName());
??????System.out.println(ClassLayout.parseInstance(a).toPrintable());
???}
}main線程來進(jìn)行加鎖操作,main線程第一次來加鎖,直接變成了輕量級(jí)鎖,這是為什么呢,不應(yīng)該是偏向鎖嘛。
這是因?yàn)椋簩?duì)這個(gè)對(duì)象A進(jìn)行了一個(gè)hashcode計(jì)算,那么hashcode占據(jù)了56個(gè)bit,鎖的一個(gè)狀態(tài)就變成了無鎖,不可偏向狀態(tài)。沒有辦法進(jìn)行偏向,第一個(gè)線程來獲取鎖的時(shí)候就會(huì)變成輕量級(jí)鎖。

此時(shí)我們把計(jì)算hashcode注釋掉
public?static?void?main(String[] args) {
???a=new?A();
???System.out.println(ClassLayout.parseInstance(a).toPrintable());
?
???lock();
???new?Thread(()->{
??????lock();
???}).start();
}
public??static??void??lock(){
???synchronized (a){
??????System.out.println("線程名稱=="+Thread.currentThread().getName());
??????System.out.println(ClassLayout.parseInstance(a).toPrintable());
???}
}
————————————————
版權(quán)聲明:本文為CSDN博主「只穿T恤的程序員」的原創(chuàng)文章,遵循CC 4.0?BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_36114346/article/details/107488920運(yùn)行代碼得到一個(gè) main線程加鎖之后,對(duì)象頭中的鎖的一個(gè)狀態(tài)變成了無鎖 可偏向,但是多了一個(gè)線程ID。

線程Thread-0運(yùn)行的一個(gè)情況,此時(shí)線程Thread-0變成了輕量級(jí)鎖,線程ID為thread-0,發(fā)生了一個(gè)鎖的膨脹。
但是先需要撤銷偏向鎖,再把鎖的狀態(tài)變成輕量級(jí)鎖,而輕量級(jí)鎖是一個(gè)CAS操作。相對(duì)撤銷偏向鎖來說來消耗的性能要低的多。

當(dāng)再執(zhí)行一個(gè)線程Thread-1時(shí),此時(shí)就變成了重量級(jí)鎖。從輕量級(jí)鎖變成了重量級(jí)鎖。

到這里我們分析完了一個(gè)鎖的狀態(tài),包括鎖膨脹情況。
畫圖總結(jié)鎖的一個(gè)膨脹
沒有進(jìn)行HashCode運(yùn)算的流程

進(jìn)行過HashCode運(yùn)算的流程

基本使用
synchronzied加鎖的對(duì)象為A,叫對(duì)象鎖 ,修飾的是代碼塊,進(jìn)入同步代碼塊之前要獲取鎖。
public??static??void??lock(){
???synchronized (a){
??????System.out.println("線程名稱=="+Thread.currentThread().getName());
??????System.out.println(ClassLayout.parseInstance(a).toPrintable());
???}
}synchronzied在靜態(tài)方法上加鎖,加鎖的類型是這個(gè)方法對(duì)應(yīng)的類鎖。進(jìn)入同步方法之前要獲取鎖。
public???static????synchronized??void??lock2(){
}synchronzied在實(shí)列方法上加鎖,加鎖的類型是這個(gè)方法對(duì)應(yīng)的對(duì)象實(shí)列鎖。進(jìn)入同步方法之前要當(dāng)前對(duì)象實(shí)列鎖。
public?????synchronized??void??lock2(){
}————————————————
版權(quán)聲明:本文為CSDN博主「只穿T恤的程序員」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/weixin_36114346/article/details/107488920


??? ?
感謝點(diǎn)贊支持下哈?
