Java Synchronized的偏向锁详细分析

作者:程序员李哈 时间:2021-07-09 00:35:23 

上篇文章已经对Synchronized关键字做了初步的介绍,从字节码层面介绍了Synchronized关键字,最终字节码层面就是monitorenter和monitorexit字节码指令。并且拿Synchronized关键字和Java的JUC包下的ReentrantLock做了比较。Synchronized关键字的初体验-超链接地址

那么本篇文章将开始深入解析Synchronized关键字的底层原理,也就是解析Hotspot虚拟机对monitorenter和monitorexit字节码指令的实现原理。

理论知识

相信各位读者在准备面试中,都会背到关于Synchronized关键字的面试题,什么对象头、锁标志位、偏向锁、轻量级锁、重量级锁,锁升级的过程等等面试题。而对于一些不仅仅只想漂浮于表面的读者来说,去看Synchronized底层源码,只能说是一头雾水。所以笔者有考虑这方面,所以理论知识(给临时抱佛脚背理论的读者)和底层源码(给喜欢研究底层源码的读者)都会在这个系列中。

偏向锁存在的意义:

先从字面意思来解释,偏向于某个线程,是不是可以理解为偏向的这个线程获取锁都很效率呢?那么为什么要存在偏向锁呢?读者需要明白,任何框架存在的意义不仅仅是为了某一部分场景,肯定需要适配大部分场景,而Synchronized关键字使用的场景可能并发高,可能并发低,可能几乎不存在并发,所以实现者需要帮用户去适配不同的场景,达到效率最高化。而对于几乎不存在并发的场景,是不是可以理解为几乎只有一个线程拿到Synchronized锁,所以就存在偏向锁去优化这种场景,不让所有场景都去走很复杂的逻辑。

偏向锁实现的流程:

  • 拿到锁竞争对象

  • 从当前线程栈中获取到一个没有使用的BasicObjectLock(用于记录锁状态)

  • 查看当前是否开启了偏向锁模式

  • 查看当前偏向锁是否偏向的是当前线程,如果偏向的是当前线程,直接退出(可以理解成命中缓存)

  • 查看当前是否已经锁升级了,并且尝试撤销偏向锁(想象一下并发过程中,可能其他线程已经完成了锁对象的锁升级)

  • 当前epoch是否发生了改变,如果发生了改变,当前线程可以尝试获取偏向锁,尝试成功直接退出

  • 当前是否是匿名偏向,或者已经偏向于某个线程,但是不是当前线程,此时可以尝试获取锁,获取成功直接退出

  • 如果不支持偏向锁或者第5步的撤销偏向锁失败了,此时尝试膨胀成轻量级锁,如果轻量级锁膨胀失败了就继续往上锁膨胀

流程图如下(仅只有偏向锁逻辑)

Java Synchronized的偏向锁详细分析

源码论证

首先,我们先需要知道Synchronized底层源码的入口在哪里,在字节码层面表示为monitorenter和monitorexit字节码指令,而我们知道JVM是负责执行字节码,最终转换成不同CPU平台的ISA指令集(也称之为跨平台)。而JVM执行字节码分为

  • CPP解释执行

  • 模板解释执行(汇编)

  • JIT编译执行

一级一级的优化,而最根本是CPP解释执行,后者都是基于CPP解释执行的不断优化,后者的难度极大,所以读者弄明白CPP解释执行就即可。

在Hotspot源码中,CPP解释执行的入口在bytecodeInterpreter.cpp文件(这里要注意,JDK1.8不同版本对synchronized关键字实现有区别,所以本文选的是jdk8u40版本,其他版本可能没有偏向锁等等逻辑)

首先,读者明白,使用Synchronized关键字时需要一个锁对象,而底层就是操作这个锁对象的对象头,所以我们先从markOop.hpp文件中找到对象头的描述信息,是不是跟外面8股文描述的一模一样呢😏

Java Synchronized的偏向锁详细分析

Java Synchronized的偏向锁详细分析

对象头熟悉以后,源码中就是操作对象头,不同的锁状态设置不同对象头,用对象头来表示不同的锁状态,替换对象头的原子性依靠CAS来保证。如果存在并发,那么CAS竞争失败的线程就会往下走,一步一步的锁升级,反而如果没有竞争那就默认使用偏向锁。

下面是Hotspot中C++解释器对于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
       // 从当前线程栈中找到一个没有被使用的BasicObjectLock
       // 作用:用来记录锁状态
       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) {
         // 抢坑,为什么这里不需要CAS,因为属于线程栈(线程变量),线程安全。
         entry->set_obj(lockee);
         int success = false;
         // 得到epoch的掩码。
         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
         // 当前是偏向锁模式,可以用JVM参数UseBiasedLocking控制
         if (mark->has_bias_pattern()) {
           uintptr_t thread_ident;
           uintptr_t anticipated_bias_locking_value;
           thread_ident = (uintptr_t)istate->thread();
           // lockee->klass()->prototype_header() 是否拿到对象的类模板的头部信息。
           // lockee->klass()->prototype_header() | thread_ident) 是类模板头部信息组合上线程id
           // mark 是当前锁对象的头部信息。
           // markOopDesc::age_mask_in_place 是当前对象的年龄信息。
           // 所以与年龄无关
           // 所以拿锁对象的原型对象的对象头控制
           // lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark 如果为0 代表当前对象头偏向锁偏向了当前线程
           anticipated_bias_locking_value =
             (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
             ~((uintptr_t) markOopDesc::age_mask_in_place);
           // 等于0代表当前锁对象头部和类模板头部一样。
           // 所以这是一次偏向锁的命中。
           if  (anticipated_bias_locking_value == 0) {
             // already biased towards this thread, nothing to do
             if (PrintBiasedLockingStatistics) {
               (* BiasedLocking::biased_lock_entry_count_addr())++;
             }
             success = true;
           }
           // 当前对象头已经膨胀成轻量级或者重量级锁了。也即非偏向锁。
           else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
             // try revoke bias
             // 尝试撤销偏向锁
             markOop header = lockee->klass()->prototype_header();
             if (hash != markOopDesc::no_hash) {
               header = header->copy_set_hash(hash);
             }
             // CAS尝试取消偏向
             if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
               if (PrintBiasedLockingStatistics)
                 (*BiasedLocking::revoked_lock_entry_count_addr())++;
             }
           }
           // 来到这里可能表示当前偏向于其他线程。
           // 而epoch发生了变动,表示批量撤销偏向锁了。
           // 当前线程可以尝试争抢一次偏向锁,没有成功就去锁升级
           else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
             // try rebias
             // 尝试重偏向
             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竞争,重偏向。
             if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
               if (PrintBiasedLockingStatistics)
                 (* BiasedLocking::rebiased_lock_entry_count_addr())++;
             }
             // CAS失败,锁升级
             else {
                 // 锁升级逻辑
               CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
             }
             success = true;
           }
           // 来到这里表示,当前是匿名偏向锁(也即暂时还没有线程占用)
           // 或者是已经偏向了某个线程,所以这里CAS尝试一次
           else {
             // try to bias towards thread in case object is anonymously biased
             markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
                                                             (uintptr_t)markOopDesc::age_mask_in_place |
                                                             epoch_mask_in_place));
             if (hash != markOopDesc::no_hash) {
               header = header->copy_set_hash(hash);
             }
             markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
             // debugging hint
             DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
             // 如果是匿名偏向,这个CAS就有可能成功
             // 如果是已经偏向其他线程,这个CAS不能成功,直接往锁升级走
             if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
               if (PrintBiasedLockingStatistics)
                 (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
             }
             // cas失败
             else {
                 // 锁升级逻辑
               CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
             }
             success = true;
           }
         }
         // traditional lightweight locking
         // case1:如果当前已经锁升级了
         // case2:如果当前不支持偏向锁
         if (!success) {
           markOop displaced = lockee->mark()->set_unlocked();
           entry->lock()->set_displaced_header(displaced);
           bool call_vm = UseHeavyMonitors;
           // UseHeavyMonitors是JVM参数,是否直接开启重量级锁
           // 如果不直接开启,就CAS竞争轻量级锁,竞争成功就直接返回
           if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
             // Is it simple recursive case?
             // CAS失败可能是锁重入,如果不是锁重入,那么就是竞争失败要往锁升级逻辑走了。
             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);
       } else {
         istate->set_msg(more_monitors);
         UPDATE_PC_AND_RETURN(0); // Re-execute
       }
     }

要明白偏向锁对应的对象头的几个部分的意义,然后带入到源码中就比较容易理解。

  • 线程对象:偏向于那个线程(当没有线程对象时,就代表是匿名偏向,此时线程都可以去竞争)

  • epoch:是否发生了批量锁撤销(为什么要锁撤销?因为偏向锁升级为轻量级锁就需要撤销)

  • 偏向锁标志位:0表示无锁,1表示偏向锁(偏向锁和无锁的锁标志位都是01)

  • 锁标志位:表示不同锁状态,偏向锁表示为01(要注意无锁也是表示为01,所以需要额外的偏向锁标志位来区分是无锁还是偏向锁)

来源:https://blog.csdn.net/qq_43799161/article/details/128674986

标签:Java,synchronized,偏向锁
0
投稿

猜你喜欢

  • springcloud项目占用内存好几个G导致服务器崩溃的问题

    2023-03-30 09:54:25
  • 一文掌握MyBatis Plus的条件构造器方法

    2023-06-18 13:00:26
  • java 设计模式之单例模式

    2021-10-07 22:00:46
  • 浅谈Java(SpringBoot)基于zookeeper的分布式锁实现

    2023-11-16 08:14:56
  • 简单了解Spring中常用工具类

    2021-09-24 10:44:28
  • Maven 错误找不到符号的解决方法

    2021-07-19 09:03:02
  • Springboot上传文件时提示405问题及排坑过程

    2022-12-13 03:03:58
  • springboot启动脚本start.sh和停止脚本 stop.sh的详细教程

    2022-10-11 08:28:26
  • Intellij IDEA中启动多个微服务(开启Run Dashboard管理)

    2022-01-11 02:25:00
  • 使用SpringBoot 配置Oracle和H2双数据源及问题

    2023-10-04 14:06:11
  • SpringBoot2之PUT请求接收不了参数的解决方案

    2023-08-23 01:32:07
  • Java的“Goto”与标签及使用详解

    2023-11-11 03:56:09
  • 浅谈virtual、abstract方法和静态方法、静态变量理解

    2022-08-29 02:29:14
  • Java动态 代理和AOP应用示例

    2023-11-26 07:45:02
  • 详解Jackson 使用以及性能介绍

    2023-02-21 00:08:31
  • Android TextView跑马灯效果实现方法

    2023-09-27 04:16:09
  • Java中的Struts2框架拦截 器之实例代码

    2023-06-21 19:04:03
  • Java实战之用hutool-db实现多数据源配置

    2023-11-28 19:37:10
  • android中使用SharedPreferences进行数据存储的操作方法

    2023-06-16 17:37:42
  • 详解Java类型擦除机制

    2023-10-29 06:41:21
  • asp之家 软件编程 m.aspxhome.com