多线程与高并发多线程java

Synchronized原理

2021-09-19  本文已影响0人  天还下着毛毛雨

[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来实现同步和锁的升级的。

锁对象

不同的修饰方式锁住的对象也不同。

  1. 修饰静态方法,锁的对象是 Class对象
  2. 修饰实例方法, 锁的this对象
  3. 代码块中 synchronized(某个对象),Class对象或者任何类的实例对象都可以

对象布局

java对象在jvm中以8字节的整数倍存在,

包含 对象头和实例数据 和对齐填充数据

基本对象布局

对象头布局

对象头由markword和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

由于笔记本是小端模式,高位字节在后,低位字节在前,反过来正好是这样的

无锁可偏向markword

2. 无锁不可偏向

由于偏向锁需要记录线程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,表示不可偏向

无锁不可偏向markword

3. 已偏向

当对象没有生成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了

即使偏向锁释放了,这些信息还在, 为了下一个加锁判断是否是偏向自己

已偏向markword

4. 轻量锁

轻量锁升级 :当锁被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));
    }
输出结果
image

5. 重量锁

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失败,都会升级为重量锁。

比如

  1. 匿名偏向,cas对象头为当前线程id失败

  2. 重偏向cas替换当前线程id 失败

  3. 轻量锁 cas对象头失败

重量锁会创建Monitor对象,锁对象markword前62位存储的是关联的Monitor对象的指针

Monitor对象
Monitor对象

Monitor对象设计的和aqs的源码差不多。

EntryList

和AQS里的Node节点一样,是一个等待队列, 只不过顺序唤醒的顺序和AQS是相反的

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
        }
      }
上一篇下一篇

猜你喜欢

热点阅读