Synchronized原理

作者: 天还下着毛毛雨 | 来源:发表于2021-09-19 22:57 被阅读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 : 包含了锁的信息,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

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

    无锁可偏向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是相反的

    • 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
            }
          }
    

    相关文章

      网友评论

        本文标题:Synchronized原理

        本文链接:https://www.haomeiwen.com/subject/qkzpgltx.html