Java必会的Synchronized底层原理剖析

 更新时间:2022年10月19日 15:33:50   作者:一灯架构  
synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用。但不可否认的是synchronized依然是并发首选工具,本文就来详细讲讲

synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用。

但不可否认的是synchronized依然是并发首选工具,连volatile、CAS、ReentrantLock都无法动摇synchronized的地位。synchronized是工作面试中的必备技能,今天就跟着一灯一块深入剖析synchronized的底层原理。

1. synchronized作用

synchronized是Java提供一种隐式锁,无需开发者手动加锁释放锁。保证多线程并发情况下数据的安全性,实现了同一个时刻只有一个线程能访问资源,其他线程只能阻塞等待,简单说就是互斥同步。

2. synchronized用法

先看一下synchronized有哪几种用法?

使用位置被锁对象示例代码
实例方法实例对象public synchronized void method() {
……
}
静态方法class类public static synchronized void method() {
……
}
实例对象实例对象public void method() {
Object obj = new Object();
synchronized (obj) {
……
}
}
类对象class类public void method() {
synchronized (Demo.class) {
……
}
}
this关键字实例对象public void method() {
synchronized (this) {
……
}
}

可以看到被锁对象只要有两种,实例对象和class类。

  • 由于静态方法可以通过类名直接访问,所以它跟直接加锁在class类上是一样的。
  • 当在实例方法、实例对象、this关键字上面加锁的时候,锁定范围都是当前实例对象。
  • 实例对象上面的锁和class类上面的锁,两者不互斥。

3. synchronized加锁原理

当我们使用synchronized在方法和对象上加锁的时候,Java底层到底怎么实现加锁的?

当在类对象上加锁的时候,也就是在class类加锁,代码如下:

/**
 * @author 一灯架构
 * @apiNote Synchronized示例
 **/
public class SynchronizedDemo {

    public void method() {
        synchronized (SynchronizedDemo.class) {
            System.out.println("Hello world!");
        }
    }

}

反编译一下,看一下源码实现:

可以看到,底层是通过monitorentermonitorexit两个关键字实现的加锁与释放锁,执行同步代码之前使用monitorenter加锁,执行完同步代码使用monitorexit释放锁,抛出异常的时候也是用monitorexit释放锁。

写成伪代码,类似下面这样:

/**
 * @author 一灯架构
 * @apiNote Synchronized示例
 **/
public class SynchronizedDemo {

    public void method() {
        try {
            monitorenter 加锁;
            System.out.println("Hello world!");
            monitorexit 释放锁;
        } catch (Exception e) {
            monitorexit 释放锁;
        }
    }

}

当在实例方法上加锁,底层是怎么实现的呢?代码如下:

/**
 * @author 一灯架构
 * @apiNote Synchronized示例
 **/
public class SynchronizedDemo {

    public static synchronized void method() {
        System.out.println("Hello world!");
    }

}

再反编译看一下底层实现:

这次只使用了一个ACC_SYNCHRONIZED关键字,实现了隐式的加锁与释放锁。其实无论是ACC_SYNCHRONIZED关键字,还是monitorentermonitorexit,底层都是通过获取monitor锁来实现的加锁与释放锁。

monitor锁又是通过ObjectMonitor来实现的,虚拟机中ObjectMonitor数据结构如下(C++实现的):

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // WaitSet 和 EntryList 的节点数之和
    _waiters      = 0,
    _recursions   = 0; // 重入次数
    _object       = NULL;
    _owner        = NULL; // 持有锁的线程
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; // 多个线程争抢锁,会先存入这个单向链表
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

图上展示了ObjectMonitor的基本工作机制:

  • 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中等待。
  • 当某个线程获取到对象的Monitor锁后进入临界区域,并把Monitor中的 _owner 变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。
  • 若持有Monitor的线程调用 wait() 方法,将释放当前持有的Monitor锁,_owner变量恢复为null,_count减1,同时该线程进入 _WaitSet 集合中等待被唤醒。
  • 在_WaitSet 集合中的线程会被再次放到_EntryList 队列中,重新竞争获取锁。
  • 若当前线程执行完毕也将释放Monitor并复位变量的值,以便其他线程进入获取锁。

线程争抢锁的过程要比上面展示得更加复杂。除了_EntryList 这个双向链表用来保存竞争的线程,ObjectMonitor中还有另外一个单向链表 _cxq,由两个队列来共同管理并发的线程。

以上就是Java必会的Synchronized底层原理剖析 的详细内容,更多关于Java Synchronized原理的资料请关注脚本之家其它相关文章!

相关文章

  • IDEA maven compile报错OutOfMemoryError(内存溢出)解决及jvm分析

    IDEA maven compile报错OutOfMemoryError(内存溢出)解决及jvm分析

    遇到Maven编译时报OutOfMemoryError错误通常因为默认的堆内存大小不足,本文就来介绍一下OutOfMemoryError(内存溢出)解决,具有一定的参考价值,感兴趣的可以了解一下
    2024-10-10
  • Java Spring JdbcTemplate基本使用详解

    Java Spring JdbcTemplate基本使用详解

    JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤
    2021-10-10
  • Spring中过滤器(Filter)和拦截器(Interceptor)的区别和联系解析

    Spring中过滤器(Filter)和拦截器(Interceptor)的区别和联系解析

    在我们日常的开发中,我们经常会用到Filter和Interceptor,这篇文章主要介绍了Spring中过滤器(Filter)和拦截器(Interceptor)的区别和联系 ,需要的朋友可以参考下
    2022-10-10
  • JVM 方法调用之动态分派(详解)

    JVM 方法调用之动态分派(详解)

    下面小编就为大家带来一篇JVM 方法调用之动态分派(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Java基础学习之集合底层原理

    Java基础学习之集合底层原理

    今天带大家回顾Java基础的相关知识,文中对集合底层原理作了非常详细的图文介绍,对Java初学者有非常好的帮助,需要的朋友可以参考下
    2021-05-05
  • 详解Java 集合类 List 的那些坑

    详解Java 集合类 List 的那些坑

    这篇文章主要介绍了Java 集合类 List 的那些坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • java模拟微信抢红包的实例代码

    java模拟微信抢红包的实例代码

    现在抢红包的功能很受欢迎,本篇文章主要介绍了java模拟微信抢红包的实例代码。具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • Scala中优雅的处理Null问题

    Scala中优雅的处理Null问题

    Spark 采用混合方式,大部分情况下使用 Option,但个别时候出于性能原因才使用了null。一个很好的习惯是当有方法返回值可能为null的时候,使用Option来代替,本文给大家介绍Scala处理Null的知识详解,一起看看吧
    2021-08-08
  • Java图片读取ImageIO.read()报错问题及解决

    Java图片读取ImageIO.read()报错问题及解决

    在使用imageio库读取图片时,如果路径中包含中文,可能会导致读取失败,解决方法是将路径中的中文字符进行转义处理,可以使用ImageUtil.java工具类进行路径转义,从而避免错误,这是一个常见问题,希望本文的解决方案能帮助到遇到相同问题的开发者
    2024-10-10
  • 关于spring的自定义缓存注解分析

    关于spring的自定义缓存注解分析

    这篇文章主要介绍了关于spring的自定义缓存注解分析,因为所有的key的失效时间都一样,要想实现不同的key不同的失效时间,就得需要自定义缓存注解,需要的朋友可以参考下
    2023-05-05

最新评论