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类上面的锁,两者不互斥 。
当在类对象上加锁的时候,也就是在class类加锁,代码如下:
/** * @author 一灯架构 * @apiNote Synchronized示例 **/public class SynchronizedDemo {public void method() {synchronized (SynchronizedDemo.class) {System.out.println("Hello world!");}}}
反编译一下,看一下源码实现:文章插图
可以看到,底层是通过monitorenter和monitorexit两个关键字实现的加锁与释放锁,执行同步代码之前使用monitorenter加锁,执行完同步代码使用monitorexit释放锁,抛出异常的时候也是用monitorexit释放锁 。
写成伪代码 , 类似下面这样:
/** * @author 一灯架构 * @apiNote Synchronized示例 **/public class SynchronizedDemo {public void method() {try {monitorenter 加锁;System.out.println("Hello world!");monitorexit 释放锁;} catch (Exception e) {monitorexit 释放锁;}}}
当在实例方法上加锁,底层是怎么实现的呢?代码如下:【Java程序员必会Synchronized底层原理剖析】
/** * @author 一灯架构 * @apiNote Synchronized示例 **/public class SynchronizedDemo {public static synchronized void method() {System.out.println("Hello world!");}}
再反编译看一下底层实现:文章插图
这次只使用了一个ACC_SYNCHRONIZED关键字,实现了隐式的加锁与释放锁 。其实无论是ACC_SYNCHRONIZED关键字,还是monitorenter和monitorexit , 底层都是通过获取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 队列中,重新竞争获取锁 。
推荐阅读
- 通过netty把百度地图API获取的地理位置从Android端发送到Java服务器端
- 1 Java I/O:模型与流
- 五 SpringBoot - Java8 新特性
- JetBrains Fleet初体验,如何运行一个java项目
- 【Java】Java中的零拷贝
- 一篇文章让你搞懂Java中的静态代理和动态代理
- 不允许还有Java程序员不了解BlockingQueue阻塞队列的实现原理
- 聊一聊被 .NET程序员 遗忘的 COM 组件
- 4 Java注解:一个真实的Elasticsearch案例
- 原生JavaScript