[TOC]
作用
是一把能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼的jdk內(nèi)置同步鎖,可以達(dá)到保證并發(fā)安全的效果
使用方式
修飾代碼塊
public void syncCodeBlock(){
synchronized (this){
System.out.println("synchronized 修飾代碼塊");
}
}
字節(jié)碼
0 aload_0
1 dup
2 astore_1
// 進(jìn)入同步塊
3 monitorenter
4 aload_1
// 退出同步塊
5 monitorexit
6 goto 14 (+8)
9 astore_2
10 aload_1
11 monitorexit
12 aload_2
13 athrow
14 return
修飾方法
public synchronized void syncMethod(){
System.out.println("synchronized 修飾方法");
}
字節(jié)碼
在方法內(nèi)部的指令中沒有任何變化,只是在方法的訪問標(biāo)志上多了個(gè) synchronized
原理
Synchronized關(guān)鍵字是操作鎖對(duì)象的對(duì)象的markword來(lái)實(shí)現(xiàn)同步和鎖的升級(jí)的源哩。
鎖對(duì)象
不同的修飾方式鎖住的對(duì)象也不同放祟。
- 修飾靜態(tài)方法,鎖的對(duì)象是 Class對(duì)象
- 修飾實(shí)例方法, 鎖的this對(duì)象
- 代碼塊中 synchronized(某個(gè)對(duì)象),Class對(duì)象或者任何類的實(shí)例對(duì)象都可以
對(duì)象布局
java對(duì)象在jvm中以8字節(jié)的整數(shù)倍存在,
包含 對(duì)象頭和實(shí)例數(shù)據(jù) 和對(duì)齊填充數(shù)據(jù)
對(duì)象頭布局
對(duì)象頭由markword和klasspoint組成
- markword : 包含了鎖的信息,gc信息,hashcode等
- Klasspoint : 存儲(chǔ)的是指向?qū)ο笤獢?shù)據(jù)的指針
markword
jvm源碼中對(duì)64位系統(tǒng)中對(duì)象頭markword的布局解釋是這樣的
markOop.hpp :
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
偏向標(biāo)識(shí) :biased_lock :1
1bit | 偏向標(biāo)識(shí) |
---|---|
0 | 不可偏向 |
1 | 可偏向 |
鎖標(biāo)識(shí) :lock:2
2bit | 鎖標(biāo)識(shí) |
---|---|
00 | 輕量鎖 |
01 | 無(wú)鎖 |
10 | 重量 |
不同的對(duì)象由于鎖狀態(tài)的不同 對(duì)象頭鎖存儲(chǔ)的信息也會(huì)不同
不同鎖狀態(tài)下的markword
jol-core jar
可以借助這個(gè)工具打印對(duì)象的布局信息
maven 依賴 :
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.13</version>
<scope>provided</scope>
</dependency>
1. 無(wú)鎖可偏向
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
示例代碼
public static void main(String[] args) throws InterruptedException {
A a = new A();
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
打印結(jié)果
由于筆記本是小端模式,高位字節(jié)在后,低位字節(jié)在前,反過來(lái)正好是這樣的
2. 無(wú)鎖不可偏向
由于偏向鎖需要記錄線程id,以標(biāo)識(shí)鎖偏向于哪個(gè)線程。線程id需要54位+2位 偏向時(shí)間戳記錄在markword中,但是對(duì)象生成了hashcode同樣記錄在markword中,導(dǎo)致線程id
沒有空間記錄,所以
對(duì)象生成了hashcode之后是 不可偏向的
示例代碼
調(diào)用對(duì)象的hashcode方法
public static void main(String[] args) throws InterruptedException {
A a = new A();
a.hashCode();
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
輸出結(jié)果
反轉(zhuǎn)小端模式, 最后8 是 0000 0001 ,發(fā)現(xiàn)生成了hashcode之后,是否可偏向標(biāo)識(shí)已經(jīng) 被改成了 0,表示不可偏向
3. 已偏向
當(dāng)對(duì)象沒有生成hashcode,并且jvm沒有禁用偏向鎖,那么當(dāng)?shù)谝粋€(gè)線程持有鎖的時(shí)候,就會(huì)在鎖對(duì)象的markword中記錄下線程id:54位 + epoch : 2
實(shí)例代碼
public static void main(String[] args) throws InterruptedException {
A a = new A();
// 線程持有過鎖
new Thread(()->{
synchronized (a){
System.out.println(111);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(2000);
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
輸出結(jié)果
最后面八位依然是 0 0000對(duì)象年齡 1 可偏向 01 輕量鎖
只不過前面56位已經(jīng)變成 線程id:54 + epoch:2了
即使偏向鎖釋放了,這些信息還在, 為了下一個(gè)加鎖判斷是否是偏向自己
4. 輕量鎖
輕量鎖升級(jí) :當(dāng)鎖被A線程持有之后刷晋,并且成為偏向鎖,不管A線程有沒有釋放鎖, 只要B線程來(lái)持有鎖,發(fā)現(xiàn)鎖對(duì)象有線程id,并且不等于自己的線程id,就會(huì)撤銷偏向鎖,升級(jí)為輕量鎖。
此時(shí),鎖狀態(tài)會(huì)變?yōu)?01,并且前62位存儲(chǔ)的會(huì)是 B線程私有棧中的lock_record對(duì)象的指針福压。
輕量鎖釋放后:會(huì)清除markword中線程私有棧的lock_record指針
示例代碼
先用A線程將鎖升級(jí)為偏向鎖,再B線程加鎖,在同步代碼塊中以后B線程釋放鎖之后,打印對(duì)象頭信息
public static void main(String[] args) throws InterruptedException {
A a = new A();
// 沒有被任何線程加鎖,打印對(duì)象頭信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
// 升級(jí)為偏向鎖 :第一個(gè)線程持有鎖
new Thread(()->{
synchronized (a){
}
}).start();
Thread.sleep(3000);
// 偏向鎖釋放后掏秩,打印對(duì)象頭信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
// 升級(jí)為輕量鎖 :另一個(gè)線程在前一個(gè)線程釋放鎖之后加鎖
new Thread(()->{
synchronized (a){
// 同步代碼塊中 輕量鎖持有的時(shí)候,打印對(duì)象頭信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
}).start();
Thread.sleep(3000);
// 輕量鎖釋放后,打印對(duì)象頭
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
輸出結(jié)果
5. 重量鎖
Block_lock : 2 變成 10
鎖釋放后還是markword里的Monitor對(duì)象指針不會(huì)被清空
鎖的膨脹過程(源碼解析)
1. 無(wú)鎖
當(dāng)一個(gè)對(duì)象被創(chuàng)建出來(lái),沒有被任何一個(gè)對(duì)象作為Synchroinize鎖對(duì)象,那么就是無(wú)鎖狀態(tài)。對(duì)象頭后三位 : 1 01
2. 偏向鎖
已偏向&偏向自己
markword中,線程id有值,但是epoch值有效,表示只有一個(gè)線程持有這個(gè)鎖,并且鎖偏向于這個(gè)線程
偏向的線程再次拿鎖,判斷鎖對(duì)象中線程id是不是自己,是的話直接拿到偏向鎖
//當(dāng)前線程是偏向鎖并且偏向自己
if(anticipated_bias_locking_value == 0) {
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
// 如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖荆姆,success不是true蒙幻,直接嘗試升級(jí)為輕量鎖
}else if(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// Class的對(duì)象頭(此時(shí)已經(jīng)被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 如果鎖對(duì)象的markword沒有改變,cas將class對(duì)象的markword設(shè)置到鎖對(duì)象中
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
// 重偏向邏輯
}else if(anticipated_bias_locking_value & epoch_mask_in_place) !=0){
......
// 匿名偏向邏輯
else {
......
}
匿名偏向
這個(gè)狀態(tài)下鎖對(duì)象的markword中沒有線程id,意味著該鎖不偏向任何線程,加鎖的線程cas設(shè)置鎖的線程id后,即可獲取鎖,進(jìn)行業(yè)務(wù)代碼的執(zhí)行
cas失敗的情況
對(duì)象頭被更改,包括鎖狀態(tài),hashcode生成都會(huì)失敗,會(huì)升級(jí)為重量鎖
源碼
//當(dāng)前線程是自己,重偏向鎖
if(){
....
// 如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖,success不是true
}else if(){
....
// 偏向鎖過期(批量重偏向),lockee類的對(duì)象作為偏向鎖,在被其他線程升級(jí)到輕量鎖的過程中,會(huì)先撤銷偏向鎖再升級(jí)為輕量鎖,
// 如果這個(gè)過程超過20次,會(huì)將lockee類的鎖對(duì)象都設(shè)置為可重偏向(markword的線程id位設(shè)置為a線程的線程id)
}else if(){
// 重偏向的邏輯
}
// 重點(diǎn)看這里
else {
// try to bias towards thread in case object is anonymously biased
// 匿名偏向
// 先獲取之前的對(duì)象頭 00000000 101
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
epoch_mask_in_place));
// hash一開始初始化為 沒有hash的狀態(tài)
// 如果在升級(jí)偏向鎖的過程中,被其他線程生成了hashcode,那么之前上面這行代碼里獲取header就會(huì)有hash值,替換成無(wú)hash的狀態(tài),那么 在下面的cas過程中,無(wú)hash的header和內(nèi)存地址中有hash的header就會(huì)不一樣,就會(huì)cas失敗,升級(jí)為重量鎖
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 新建一個(gè)對(duì)象頭,里面的線程id換成當(dāng)前線程id
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// 省略jvm debug代碼
....
// cas,如果之前的對(duì)象頭 和現(xiàn)在鎖對(duì)象的markword相同(沒有其他線程改變鎖的狀態(tài),也沒有生成hash),則替換成功,獲取偏向鎖成功
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// 否則升級(jí)為重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
可重偏向(偏向撤銷20以上)
markword中,線程id有值,但是epoch值無(wú)效,表示 該偏向鎖已經(jīng)過期,可以重新重新偏向于另外一個(gè)線程
原因
lockee類的對(duì)象作為偏向鎖,在被其他線程升級(jí)到輕量鎖的過程中,會(huì)先撤銷偏向鎖再升級(jí)為輕量鎖,
如果這個(gè)過程超過20次,epoch值就會(huì)無(wú)效,這時(shí)再加鎖,會(huì)將lockee類的鎖對(duì)象都設(shè)置為可重偏向(markword的原線程id位設(shè)置為當(dāng)前加鎖線程的線程id)
則會(huì)生成偏向于當(dāng)前線程的markword,cas替換鎖對(duì)象的markword
示例代碼
public static void main(String[] args) throws InterruptedException {
List<A> list = new ArrayList<>();
log.error("==============t1================");
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 30; i++) {
A a = new A();
log.error("{} ,{},加鎖前: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
synchronized (a){
log.error("{} ,{},加鎖中: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
log.error("{} ,{},加鎖后: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
list.add(a);
}
});
t1.setName("t1");
t1.start();
t1.join();
log.error("===================t2====================");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= list.size(); i++) {
A a = list.get(i-1);
log.error("{} ,{},加鎖前: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
synchronized (a){
log.error("{} ,{},加鎖中: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
log.error("{} ,{},加鎖后: {}",Thread.currentThread().getName(),i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
});
t2.setName("t2");
t2.start();
t2.join();
}
發(fā)現(xiàn)胆筒,t2到了20次的時(shí)候邮破,也就是經(jīng)歷了20次的偏向撤銷****,那么再次獲取鎖的時(shí)候仆救,又變成了偏向鎖抒和。并且偏向t2。
jvm源碼
//當(dāng)前線程是自己,重偏向鎖
if(){
....
// 如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖彤蔽,success不是true
}else if(){
....
// 偏向鎖過期(批量重偏向),lockee類的對(duì)象作為偏向鎖,在被其他線程升級(jí)到輕量鎖的過程中,會(huì)先撤銷偏向鎖再升級(jí)為輕量鎖,
// 如果這個(gè)過程超過20次,會(huì)將lockee類的鎖對(duì)象都設(shè)置為可重偏向(markword的線程id位設(shè)置為a線程的線程id)
}else if(anticipated_bias_locking_value & epoch_mask_in_place) !=0){
// 重偏向的邏輯
// try rebias
// 創(chuàng)建偏向當(dāng)前線程的markword
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
// 這里鎖hashcode被其他線程生成,header改變,cas會(huì)失敗,升級(jí)為重量鎖
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
// cas 替換markword
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
// cas失敗膨脹至重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
// 匿名偏向邏輯
else {
}
禁用偏向
如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖摧莽,success不是true,直接嘗試升級(jí)為輕量鎖
實(shí)例代碼:
public static void main(String[] args) throws InterruptedException {
List<A> list = new ArrayList<>();
log.error("==============t1================");
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 40; i++) {
A a = new A();
log.error("{} ,{},加鎖前: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
synchronized (a) {
log.error("{} ,{},加鎖中: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
log.error("{} ,{},加鎖后: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
list.add(a);
}
});
t1.setName("t1");
t1.start();
t1.join();
log.error("===================t2====================");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 40; i++) {
A a = list.get(i - 1);
log.error("{} ,{},加鎖前: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
synchronized (a) {
log.error("{} ,{},加鎖中: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
log.error("{} ,{},加鎖后: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
});
t2.setName("t2");
t2.start();
t2.join();
//注意當(dāng)t2執(zhí)行完成的時(shí)候顿痪,其實(shí)也才經(jīng)歷了20次的偏向撤銷镊辕,因?yàn)閠2執(zhí)行的時(shí)候油够,后面20次,走的都是重偏向的流程
//此時(shí)征懈,list中石咬,前20個(gè)存的是輕量鎖對(duì)象,后20個(gè)存的是偏向t2的偏向鎖對(duì)象卖哎。
log.error("==================t3=======================");
Thread t3 = new Thread(() -> {
for (int i = 1; i <= 40; i++) {
A a = list.get(i - 1);
log.error("{} ,{},加鎖前: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
synchronized (a) {
log.error("{} ,{},加鎖中: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
log.error("{} ,{},加鎖后: {}", Thread.currentThread().getName(), i, ClassLayout.parseInstance(a).toPrintableTest(a));
}
});
t3.setName("t3");
t3.start();
t3.join();
//當(dāng)t3執(zhí)行完成鬼悠,把list中后面20個(gè)偏向t2的對(duì)象,經(jīng)過偏向撤銷升級(jí)成了輕量鎖亏娜。
//所以到這里,整個(gè)A類的對(duì)象焕窝,總共經(jīng)歷了40次的偏向撤銷
A newA = new A();
log.error("newA : {}",ClassLayout.parseInstance(a).toPrintable(a));
}
直接就是001,無(wú)鎖不可偏向的標(biāo)識(shí)照藻。要知道袜啃,在我們這個(gè)程序中,前40個(gè)A類的對(duì)象被創(chuàng)建出來(lái)都是無(wú)鎖可偏向的狀態(tài)幸缕。而當(dāng)偏向撤銷達(dá)到了40次,JVM直接就將A類產(chǎn)生的對(duì)象改為了無(wú)鎖不可偏向的狀態(tài)
jvm源碼
//當(dāng)前線程是偏向鎖并且偏向自己
if(anticipated_bias_locking_value == 0) {
success = true;
// 如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖晰韵,success不是true发乔,直接嘗試升級(jí)為輕量鎖
}else if(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// Class的對(duì)象頭(此時(shí)已經(jīng)被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 如果鎖對(duì)象的markword沒有改變,cas將class對(duì)象的markword設(shè)置到鎖對(duì)象中
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
// 重偏向邏輯
}else if(anticipated_bias_locking_value & epoch_mask_in_place) !=0){
......
success = true;
// 匿名偏向邏輯
else {
......
success = true;
}
3. 輕量鎖
當(dāng)前jvm解釋_monitorenter指令時(shí), 當(dāng)偏向鎖的邏輯中,不是匿名偏向,不是偏向自己,不是可重偏向 或者 禁用偏向,都會(huì)升級(jí)為輕量鎖
jvm字節(jié)碼解釋器 bytecodeInterpreter.cpp對(duì)_monitorenter指令的解析片段 :
// 如果不是偏向鎖,或者升級(jí)偏向鎖失敗
if (!success) {
// 創(chuàng)建一個(gè)無(wú)鎖狀態(tài)的markword
markOop displaced = lockee->mark()->set_unlocked();
// 設(shè)置到鎖記錄的lock屬性中
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
// cas 如果lockee鎖對(duì)象中的markword 和無(wú)鎖狀態(tài)的markword相同,把markword替換為lock_recrod的指針.升級(jí)為輕量鎖
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
// 否則重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
// 執(zhí)行下一條指令
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
4. 重量鎖
需要膨脹為重量鎖的話,說(shuō)明鎖的競(jìng)爭(zhēng)已經(jīng)到了非常激勵(lì)的地步,上面偏向鎖,輕量鎖的過程中,任何cas失敗,都會(huì)升級(jí)為重量鎖雪猪。
比如
匿名偏向,cas對(duì)象頭為當(dāng)前線程id失敗
重偏向cas替換當(dāng)前線程id 失敗
輕量鎖 cas對(duì)象頭失敗
重量鎖會(huì)創(chuàng)建Monitor對(duì)象,鎖對(duì)象markword前62位存儲(chǔ)的是關(guān)聯(lián)的Monitor對(duì)象的指針
Monitor對(duì)象
Monitor對(duì)象設(shè)計(jì)的和aqs的源碼差不多栏尚。
EntryList
和AQS里的Node節(jié)點(diǎn)一樣,是一個(gè)等待隊(duì)列, 只不過順序喚醒的順序和AQS是相反的
AQS : 從隊(duì)列的頭部喚醒, 所以是先阻塞排隊(duì)的線程先被喚醒搶鎖。
Synchronized :是從EntryList 的尾部喚醒,后阻塞排隊(duì)的線程先喚醒搶鎖只恨。
waitSet
調(diào)用wait方法阻塞的線程隊(duì)列译仗,和aqs里的條件喚醒隊(duì)列差不多,只不過aqs里的支持多條件喚醒一部分進(jìn)去隊(duì)列等待加鎖,Monitor對(duì)象的waitSet則是喚醒全部等待的線程進(jìn)入EntryList。
Owner_thread
標(biāo)識(shí)當(dāng)前持有重量鎖的線程,aqs里也有, 可以用來(lái)判斷重量鎖重入官觅。
性能損耗最大
當(dāng)膨脹為重量鎖之后,對(duì)性能的損耗是最大的纵菌。因?yàn)樽枞枰{(diào)用到linux內(nèi)核的pthread_metux互斥量函數(shù)來(lái)阻塞線程, 自旋調(diào)用的linux內(nèi)核的spin_lock函數(shù) 等等。
都需要從用戶態(tài)切換到內(nèi)核態(tài)休涤,升級(jí)權(quán)限才能調(diào)用到os的函數(shù)咱圆。執(zhí)行完成之后,又要切換回用戶態(tài),從寄存器保存的線程狀態(tài)恢復(fù)過來(lái) 執(zhí)行下面的程序, 所謂cpu上下文切換。
moniter_enter整體源碼
lock_record對(duì)象
主要有_lock對(duì)象和一個(gè)_obj對(duì)象
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
BasicLock _lock; // the lock, must be double word aligned
oop _obj; // object holds the lock;
// 省略其他方法
}
jvm字節(jié)碼解釋器 bytecodeInterpreter.cpp對(duì)_monitorenter指令的解析:
CASE(_monitorenter): {
// 獲取鎖對(duì)象
oop lockee = STACK_OBJECT(-1);
// derefing's lockee ought to provoke implicit null check
CHECK_NULL(lockee);
// find a free monitor or one already allocated for this object
// if we find a matching object then we need a new monitor
// since this is recursive enter
// lockRecord 鎖記錄
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
if (entry != NULL) {
// 將鎖對(duì)象設(shè)置到鎖記錄中
entry->set_obj(lockee);
int success = false;
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
markOop mark = lockee->mark();
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// implies UseBiasedLocking
// 如果沒有禁用偏向鎖
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread();
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
// 當(dāng)前線程是偏向鎖并且偏向自己
if (anticipated_bias_locking_value == 0) {
// already biased towards this thread, nothing to do
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
// 如果,lockee類的對(duì)象作為偏向鎖被撤銷過40次以上,會(huì)在這里禁用偏向鎖功氨,success不是true
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// class對(duì)象的對(duì)象頭(此時(shí)已經(jīng)被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 將class對(duì)象的markword設(shè)置到鎖對(duì)象中,
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
// 偏向鎖過期,lockee類的對(duì)象作為偏向鎖,在被其他線程升級(jí)到輕量鎖的過程中,會(huì)先撤銷偏向鎖再升級(jí)為輕量鎖,
// 如果這個(gè)過程超過20次,會(huì)將lockee類的鎖對(duì)象都設(shè)置為可重偏向(markword的線程id位設(shè)置為a線程的線程id)
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// try rebias
// 創(chuàng)建偏向當(dāng)前線程的markword
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
// cas 替換markword
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
// cas失敗膨脹至重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
// try to bias towards thread in case object is anonymously biased
// 匿名偏向
// 先獲取之前的對(duì)象頭 00000000 101
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
epoch_mask_in_place));
// hash一開始初始化為 沒有hash的狀態(tài)
// 如果在升級(jí)偏向鎖的過程中,被其他線程生成了hashcode,那么之前上面這行代碼里獲取header就會(huì)有hash值,替換成無(wú)hash的狀態(tài),那么 在下面的cas過程中,無(wú)hash的header和內(nèi)存地址中有hash的header就會(huì)不一樣,就會(huì)cas失敗,升級(jí)為重量鎖
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 新建一個(gè)對(duì)象頭,里面的線程id替換成當(dāng)前線程id
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
// cas,如果之前的對(duì)象頭 和現(xiàn)在鎖對(duì)象的markword相同,則替換成功(并發(fā)情況下可能在上面的過程中被別的線程升級(jí)為偏向鎖了)
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// cas失敗,膨脹重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
// traditional lightweight locking
if (!success) {
// 創(chuàng)建一個(gè)無(wú)所狀態(tài)的markword
markOop displaced = lockee->mark()->set_unlocked();
// 設(shè)置到鎖記錄當(dāng)中
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;‘
// cad 如果lockee鎖對(duì)象中的markword 和無(wú)鎖狀態(tài)的markword相同,把markword替換為entry的指針.升級(jí)為輕量鎖
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
// cas失敗,膨脹重量鎖
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // Re-execute
}
}