Java synchronized偏向锁的核心原理详解

作者:小小茶花女 时间:2022-12-26 12:11:58 

1. 偏向锁的核心原理

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。 Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。

public class Main {
   static final Object obj = new Object();
   public static void main(String[] args) {
       Thread thread = new Thread(()->{
          m1();
       });
       thread.start();
   }
   public static void m1() {
       synchronized( obj ) {
           // 同步块 A
           m2();
       }
   }
   public static void m2() {
       synchronized( obj ) {
           // 同步块 B
           m3();
       }
   }
   public static void m3() {
       synchronized( obj ) {
           //偏向状态
           // 同步块 C
       }
   }
}

偏向锁的核心原理是:如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark Word的结构变为偏向锁结构,锁对象的锁标志位(lock)被改为01,偏向标志位(biased_lock)被改为1,然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完成)。以后该线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。

偏向锁的主要作用是消除无竞争情况下的同步原语,进一步提升程序性能,所以,在没有锁竞争的场合,偏向锁有很好的优化效果。但是,一旦有第二条线程需要竞争锁,那么偏向模式立即结束,进入轻量级锁的状态。

假如在大部分情况下同步块是没有竞争的,那么可以通过偏向来提高性能。即在无竞争时,之前获得锁的线程再次获得锁时会判断偏向锁的线程ID是否指向自己,如果是,那么该线程将不用再次获得锁,直接就可以进入同步块;如果未指向当前线程,当前线程就会采用CAS操作将Mark Word中的线程ID设置为当前线程ID,如果CAS操作成功,那么获取偏向锁成功,执行同步代码块,如果CAS操作失败,那么表示有竞争,抢锁线程被挂起,撤销占锁线程的偏向锁,然后将偏向锁膨胀为轻量级锁。

Java synchronized偏向锁的核心原理详解

偏向锁的加锁过程为:新线程只需要判断内置锁对象的Mark Word中的线程ID是不是自己的ID,如果是就直接使用这个锁,而不使用CAS交换;如果不是,比如在第一次获得此锁时内置锁的线程ID为空,就使用CAS交换,新线程将自己的线程ID交换到内置锁的Mark Word中,如果交换成功,就加锁成功。

每执行一轮抢占,JVM内部都会比较内置锁的偏向线程ID与当前线程ID,如果匹配,就表明当前线程已经获得了偏向锁,当前线程可以快速进入临界区。所以,偏向锁的效率是非常高的。总之,偏向锁是针对一个线程而言的,线程获得锁之后就不会再有解锁等操作了,这样可以省略很多开销。

偏向锁的缺点:如果锁对象时常被多个线程竞争,偏向锁就是多余的,并且其撤销的过程会带来一些性能开销。

2. 偏向锁的撤销

假如有多个线程来竞争偏向锁,此对象锁已经有所偏向,其他的线程发现偏向锁并不是偏向自己,就说明存在了竞争,尝试撤销偏向锁(很可能引入安全点),然后膨胀到轻量级锁。

偏向锁撤销的开销花费还是挺大的,其大概过程如下:

(1) 在一个安全点停止拥有锁的线程。

(2) 遍历线程的栈帧,检查是否存在锁记录。如果存在锁记录,就需要清空锁记录,使其变成无锁状态,并修复锁记录指向的Mark Word,清除其线程ID。

(3) 将当前锁升级成轻量级锁。

(4) 唤醒当前线程。

所以,如果某些临界区存在两个及两个以上的线程竞争,那么偏向锁反而会降低性能。在这种情况下,可以在启动JVM时就把偏向锁的默认功能关闭。

撤销偏向锁的条件:

(1) 多个线程竞争偏向锁。

(2) 调用偏向锁对象的hashcode()方法或者System.identityHashCode()方法计算对象的HashCode之后,将哈希码放置到Mark Word中,内置锁变成无锁状态,偏向锁将被撤销。

3. 偏向锁的膨胀

如果偏向锁被占据,一旦有第二个线程争抢这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到内置锁偏向状态,这时表明在这个对象锁上已经存在竞争了。JVM检查原来持有该对象锁的占有线程是否依然存活,如果挂了,就可以将对象变为无锁状态,然后进行重新偏向,偏向为抢锁线程。

如果JVM检查到原来的线程依然存活,就进一步检查占有线程的调用堆栈是否通过锁记录持有偏向锁。如果存在锁记录,就表明原来的线程还在使用偏向锁,发生锁竞争,撤销原来的偏向锁,将偏向锁膨胀(INFLATING)为轻量级锁。

4. 偏向锁的好处

经验表明,其实大部分情况下进入一个同步代码块的线程都是同一个线程。这也是JDK会引入偏向锁的原因。所以,总体来说,使用偏向锁带来的好处还是大于偏向锁撤销和膨胀所带来的代价。

来源:https://hengheng.blog.csdn.net/article/details/123178723

标签:Java,synchronized,偏向锁,核心原理
0
投稿

猜你喜欢

  • 实例分析java对象的序列化和反序列化

    2022-07-11 00:22:41
  • java 中类似js encodeURIComponent 函数的实现案例

    2023-03-20 13:13:50
  • Tomcat内存溢出分析及解决方法

    2023-11-12 23:24:47
  • Android UI效果之绘图篇(四)

    2022-08-07 19:26:12
  • 简单介绍区分applet和application的方法

    2022-11-05 07:18:40
  • C#中的虚方法和抽象方法的运用

    2023-06-02 15:49:40
  • java String校招面试题过程详解

    2021-12-01 11:11:21
  • Android动态布局小结

    2021-10-17 12:04:39
  • java实现用户自动登录

    2023-11-10 14:38:59
  • Java设计模式之共享模式/享元模式(Flyweight模式)介绍

    2023-06-15 04:35:10
  • 关于idea的gitignore文件编写及解决ignore文件不生效问题

    2023-02-28 02:04:13
  • Spring的组合注解和元注解原理与用法详解

    2023-12-05 10:25:05
  • android 控件同时监听单击和双击实例

    2022-11-16 15:45:33
  • MybatisPlus多表连接查询的问题及解决方案

    2023-11-25 22:09:06
  • 利用Java写一个学生管理系统

    2023-09-24 17:06:54
  • SpringCloud Config统一配置中心问题分析解决与客户端动态刷新实现

    2023-08-17 17:19:48
  • Android广播实现App开机自启动

    2023-01-06 14:54:44
  • Android实现捕获TextView超链接的方法

    2021-07-03 08:44:04
  • Spring注解Autowired的底层实现原理详解

    2022-10-19 11:49:44
  • C#交错数组用法实例

    2022-08-13 09:34:55
  • asp之家 软件编程 m.aspxhome.com