Synchronized原理
[TOC]
作用
是一把能够保证在同一时刻最多只有一个线程执行该段代码的jdk内置同步锁,可以达到保证并发安全的效果
使用方式
修饰代码块
public void syncCodeBlock(){
synchronized (this){
System.out.println("synchronized 修饰代码块");
}
}
字节码
0 aload_0
1 dup
2 astore_1
// 进入同步块
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 修饰方法");
}
字节码
在方法内部的指令中没有任何变化,只是在方法的访问标志上多了个 synchronized
image原理
Synchronized关键字是操作锁对象的对象的markword来实现同步和锁的升级的。
锁对象
不同的修饰方式锁住的对象也不同。
- 修饰静态方法,锁的对象是 Class对象
- 修饰实例方法, 锁的this对象
- 代码块中 synchronized(某个对象),Class对象或者任何类的实例对象都可以
对象布局
java对象在jvm中以8字节的整数倍存在,
包含 对象头和实例数据 和对齐填充数据
基本对象布局对象头布局
对象头由markword和klasspoint组成
对象头布局- markword : 包含了锁的信息,gc信息,hashcode等
- Klasspoint : 存储的是指向对象元数据的指针
markword
jvm源码中对64位系统中对象头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)
偏向标识 :biased_lock :1
1bit | 偏向标识 |
---|---|
0 | 不可偏向 |
1 | 可偏向 |
锁标识 :lock:2
2bit | 锁标识 |
---|---|
00 | 轻量锁 |
01 | 无锁 |
10 | 重量 |
不同的对象由于锁状态的不同 对象头锁存储的信息也会不同
image不同锁状态下的markword
jol-core jar
可以借助这个工具打印对象的布局信息
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. 无锁可偏向
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
无锁可偏向markword示例代码
public static void main(String[] args) throws InterruptedException {
A a = new A();
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
打印结果
image由于笔记本是小端模式,高位字节在后,低位字节在前,反过来正好是这样的
无锁可偏向markword2. 无锁不可偏向
由于偏向锁需要记录线程id,以标识锁偏向于哪个线程。线程id需要54位+2位 偏向时间戳记录在markword中,但是对象生成了hashcode同样记录在markword中,导致线程id
没有空间记录,所以
对象生成了hashcode之后是 不可偏向的
无锁不可偏向markword示例代码
调用对象的hashcode方法
public static void main(String[] args) throws InterruptedException {
A a = new A();
a.hashCode();
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
输出结果
image反转小端模式, 最后8 是 0000 0001 ,发现生成了hashcode之后,是否可偏向标识已经 被改成了 0,表示不可偏向
无锁不可偏向markword3. 已偏向
当对象没有生成hashcode,并且jvm没有禁用偏向锁,那么当第一个线程持有锁的时候,就会在锁对象的markword中记录下线程id:54位 + epoch : 2
已偏向markword实例代码
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));
}
输出结果
image最后面八位依然是 0 0000对象年龄 1 可偏向 01 轻量锁
只不过前面56位已经变成 线程id:54 + epoch:2了
即使偏向锁释放了,这些信息还在, 为了下一个加锁判断是否是偏向自己
已偏向markword4. 轻量锁
轻量锁升级 :当锁被A线程持有之后,并且成为偏向锁,不管A线程有没有释放锁, 只要B线程来持有锁,发现锁对象有线程id,并且不等于自己的线程id,就会撤销偏向锁,升级为轻量锁。
此时,锁状态会变为001,并且前62位存储的会是 B线程私有栈中的lock_record对象的指针。
轻量锁持有中markword轻量锁释放后:会清除markword中线程私有栈的lock_record指针
轻量锁被释放后markword示例代码
先用A线程将锁升级为偏向锁,再B线程加锁,在同步代码块中以后B线程释放锁之后,打印对象头信息
public static void main(String[] args) throws InterruptedException {
A a = new A();
// 没有被任何线程加锁,打印对象头信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
// 升级为偏向锁 :第一个线程持有锁
new Thread(()->{
synchronized (a){
}
}).start();
Thread.sleep(3000);
// 偏向锁释放后,打印对象头信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
// 升级为轻量锁 :另一个线程在前一个线程释放锁之后加锁
new Thread(()->{
synchronized (a){
// 同步代码块中 轻量锁持有的时候,打印对象头信息
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
}).start();
Thread.sleep(3000);
// 轻量锁释放后,打印对象头
log.error(ClassLayout.parseInstance(a).toPrintable(a));
}
输出结果
image5. 重量锁
Block_lock : 2 变成 10
锁释放后还是markword里的Monitor对象指针不会被清空
重量锁markword锁的膨胀过程(源码解析)
1. 无锁
当一个对象被创建出来,没有被任何一个对象作为Synchroinize锁对象,那么就是无锁状态。对象头后三位 : 1 01
2. 偏向锁
已偏向&偏向自己
markword中,线程id有值,但是epoch值有效,表示只有一个线程持有这个锁,并且锁偏向于这个线程
偏向的线程再次拿锁,判断锁对象中线程id是不是自己,是的话直接拿到偏向锁
//当前线程是偏向锁并且偏向自己
if(anticipated_bias_locking_value == 0) {
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
// 如果,lockee类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true,直接尝试升级为轻量锁
}else if(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// Class的对象头(此时已经被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 如果锁对象的markword没有改变,cas将class对象的markword设置到锁对象中
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 {
......
}
匿名偏向
这个状态下锁对象的markword中没有线程id,意味着该锁不偏向任何线程,加锁的线程cas设置锁的线程id后,即可获取锁,进行业务代码的执行
cas失败的情况
对象头被更改,包括锁状态,hashcode生成都会失败,会升级为重量锁
源码
//当前线程是自己,重偏向锁
if(){
....
// 如果,lockee类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true
}else if(){
....
// 偏向锁过期(批量重偏向),lockee类的对象作为偏向锁,在被其他线程升级到轻量锁的过程中,会先撤销偏向锁再升级为轻量锁,
// 如果这个过程超过20次,会将lockee类的锁对象都设置为可重偏向(markword的线程id位设置为a线程的线程id)
}else if(){
// 重偏向的逻辑
}
// 重点看这里
else {
// try to bias towards thread in case object is anonymously biased
// 匿名偏向
// 先获取之前的对象头 00000000 101
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
epoch_mask_in_place));
// hash一开始初始化为 没有hash的状态
// 如果在升级偏向锁的过程中,被其他线程生成了hashcode,那么之前上面这行代码里获取header就会有hash值,替换成无hash的状态,那么 在下面的cas过程中,无hash的header和内存地址中有hash的header就会不一样,就会cas失败,升级为重量锁
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 新建一个对象头,里面的线程id换成当前线程id
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// 省略jvm debug代码
....
// cas,如果之前的对象头 和现在锁对象的markword相同(没有其他线程改变锁的状态,也没有生成hash),则替换成功,获取偏向锁成功
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// 否则升级为重量锁
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
可重偏向(偏向撤销20以上)
markword中,线程id有值,但是epoch值无效,表示 该偏向锁已经过期,可以重新重新偏向于另外一个线程
原因
lockee类的对象作为偏向锁,在被其他线程升级到轻量锁的过程中,会先撤销偏向锁再升级为轻量锁,
如果这个过程超过20次,epoch值就会无效,这时再加锁,会将lockee类的锁对象都设置为可重偏向(markword的原线程id位设置为当前加锁线程的线程id)
则会生成偏向于当前线程的markword,cas替换锁对象的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();
}
image
发现,t2到了20次的时候,也就是经历了20次的偏向撤销****,那么再次获取锁的时候,又变成了偏向锁。并且偏向t2。
jvm源码
//当前线程是自己,重偏向锁
if(){
....
// 如果,lockee类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true
}else if(){
....
// 偏向锁过期(批量重偏向),lockee类的对象作为偏向锁,在被其他线程升级到轻量锁的过程中,会先撤销偏向锁再升级为轻量锁,
// 如果这个过程超过20次,会将lockee类的锁对象都设置为可重偏向(markword的线程id位设置为a线程的线程id)
}else if(anticipated_bias_locking_value & epoch_mask_in_place) !=0){
// 重偏向的逻辑
// try rebias
// 创建偏向当前线程的markword
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
// 这里锁hashcode被其他线程生成,header改变,cas会失败,升级为重量锁
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类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true,直接尝试升级为轻量锁
实例代码:
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();
//注意当t2执行完成的时候,其实也才经历了20次的偏向撤销,因为t2执行的时候,后面20次,走的都是重偏向的流程
//此时,list中,前20个存的是轻量锁对象,后20个存的是偏向t2的偏向锁对象。
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();
//当t3执行完成,把list中后面20个偏向t2的对象,经过偏向撤销升级成了轻量锁。
//所以到这里,整个A类的对象,总共经历了40次的偏向撤销
A newA = new A();
log.error("newA : {}",ClassLayout.parseInstance(a).toPrintable(a));
}
输出结果
直接就是001,无锁不可偏向的标识。要知道,在我们这个程序中,前40个A类的对象被创建出来都是无锁可偏向的状态。而当偏向撤销达到了40次,JVM直接就将A类产生的对象改为了无锁不可偏向的状态
jvm源码
//当前线程是偏向锁并且偏向自己
if(anticipated_bias_locking_value == 0) {
success = true;
// 如果,lockee类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true,直接尝试升级为轻量锁
}else if(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// Class的对象头(此时已经被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 如果锁对象的markword没有改变,cas将class对象的markword设置到锁对象中
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. 轻量锁
当前jvm解释_monitorenter指令时, 当偏向锁的逻辑中,不是匿名偏向,不是偏向自己,不是可重偏向 或者 禁用偏向,都会升级为轻量锁
jvm字节码解释器 bytecodeInterpreter.cpp对_monitorenter指令的解析片段 :
// 如果不是偏向锁,或者升级偏向锁失败
if (!success) {
// 创建一个无锁状态的markword
markOop displaced = lockee->mark()->set_unlocked();
// 设置到锁记录的lock属性中
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
// cas 如果lockee锁对象中的markword 和无锁状态的markword相同,把markword替换为lock_recrod的指针.升级为轻量锁
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);
}
}
}
// 执行下一条指令
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
4. 重量锁
需要膨胀为重量锁的话,说明锁的竞争已经到了非常激励的地步,上面偏向锁,轻量锁的过程中,任何cas失败,都会升级为重量锁。
比如
-
匿名偏向,cas对象头为当前线程id失败
-
重偏向cas替换当前线程id 失败
-
轻量锁 cas对象头失败
重量锁会创建Monitor对象,锁对象markword前62位存储的是关联的Monitor对象的指针
Monitor对象
Monitor对象Monitor对象设计的和aqs的源码差不多。
EntryList
和AQS里的Node节点一样,是一个等待队列, 只不过顺序唤醒的顺序和AQS是相反的
-
AQS : 从队列的头部唤醒, 所以是先阻塞排队的线程先被唤醒抢锁。
-
Synchronized :是从EntryList 的尾部唤醒,后阻塞排队的线程先唤醒抢锁。
waitSet
调用wait方法阻塞的线程队列,和aqs里的条件唤醒队列差不多,只不过aqs里的支持多条件唤醒一部分进去队列等待加锁,Monitor对象的waitSet则是唤醒全部等待的线程进入EntryList。
Owner_thread
标识当前持有重量锁的线程,aqs里也有, 可以用来判断重量锁重入。
性能损耗最大
当膨胀为重量锁之后,对性能的损耗是最大的。因为阻塞需要调用到linux内核的pthread_metux互斥量函数来阻塞线程, 自旋调用的linux内核的spin_lock函数 等等。
都需要从用户态切换到内核态,升级权限才能调用到os的函数。执行完成之后,又要切换回用户态,从寄存器保存的线程状态恢复过来 执行下面的程序, 所谓cpu上下文切换。
moniter_enter整体源码
lock_record对象
主要有_lock对象和一个_obj对象
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字节码解释器 bytecodeInterpreter.cpp对_monitorenter指令的解析:
CASE(_monitorenter): {
// 获取锁对象
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) {
// 将锁对象设置到锁记录中
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);
// 当前线程是偏向锁并且偏向自己
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类的对象作为偏向锁被撤销过40次以上,会在这里禁用偏向锁,success不是true
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// try revoke bias
// class对象的对象头(此时已经被第40次改成禁用偏向了)
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 将class对象的markword设置到锁对象中,
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
// 偏向锁过期,lockee类的对象作为偏向锁,在被其他线程升级到轻量锁的过程中,会先撤销偏向锁再升级为轻量锁,
// 如果这个过程超过20次,会将lockee类的锁对象都设置为可重偏向(markword的线程id位设置为a线程的线程id)
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// try rebias
// 创建偏向当前线程的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
// 匿名偏向
// 先获取之前的对象头 00000000 101
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
epoch_mask_in_place));
// hash一开始初始化为 没有hash的状态
// 如果在升级偏向锁的过程中,被其他线程生成了hashcode,那么之前上面这行代码里获取header就会有hash值,替换成无hash的状态,那么 在下面的cas过程中,无hash的header和内存地址中有hash的header就会不一样,就会cas失败,升级为重量锁
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 新建一个对象头,里面的线程id替换成当前线程id
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
// cas,如果之前的对象头 和现在锁对象的markword相同,则替换成功(并发情况下可能在上面的过程中被别的线程升级为偏向锁了)
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) {
// 创建一个无所状态的markword
markOop displaced = lockee->mark()->set_unlocked();
// 设置到锁记录当中
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;‘
// cad 如果lockee锁对象中的markword 和无锁状态的markword相同,把markword替换为entry的指针.升级为轻量锁
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
}
}