IT评测·应用市场-qidao123.com技术社区

标题: Java程序员必会Synchronized底层原理剖析 [打印本页]

作者: 南飓风    时间: 2022-10-19 15:33
标题: Java程序员必会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类。
3. synchronized加锁原理

当我们使用synchronized在方法和对象上加锁的时候,Java底层到底怎么实现加锁的?
当在类对象上加锁的时候,也就是在class类加锁,代码如下:
  1. /**
  2. * @author 一灯架构
  3. * @apiNote Synchronized示例
  4. **/
  5. public class SynchronizedDemo {
  6.     public void method() {
  7.         synchronized (SynchronizedDemo.class) {
  8.             System.out.println("Hello world!");
  9.         }
  10.     }
  11. }
复制代码
反编译一下,看一下源码实现:

可以看到,底层是通过monitorentermonitorexit两个关键字实现的加锁与释放锁,执行同步代码之前使用monitorenter加锁,执行完同步代码使用monitorexit释放锁,抛出异常的时候也是用monitorexit释放锁。
写成伪代码,类似下面这样:
  1. /**
  2. * @author 一灯架构
  3. * @apiNote Synchronized示例
  4. **/
  5. public class SynchronizedDemo {
  6.     public void method() {
  7.         try {
  8.             monitorenter 加锁;
  9.             System.out.println("Hello world!");
  10.             monitorexit 释放锁;
  11.         } catch (Exception e) {
  12.             monitorexit 释放锁;
  13.         }
  14.     }
  15. }
复制代码
当在实例方法上加锁,底层是怎么实现的呢?代码如下:
  1. /**
  2. * @author 一灯架构
  3. * @apiNote Synchronized示例
  4. **/
  5. public class SynchronizedDemo {
  6.     public static synchronized void method() {
  7.         System.out.println("Hello world!");
  8.     }
  9. }
复制代码
再反编译看一下底层实现:

这次只使用了一个ACC_SYNCHRONIZED关键字,实现了隐式的加锁与释放锁。其实无论是ACC_SYNCHRONIZED关键字,还是monitorentermonitorexit,底层都是通过获取monitor锁来实现的加锁与释放锁。
monitor锁又是通过ObjectMonitor来实现的,虚拟机中ObjectMonitor数据结构如下(C++实现的):
  1. ObjectMonitor() {
  2.     _header       = NULL;
  3.     _count        = 0; // WaitSet 和 EntryList 的节点数之和
  4.     _waiters      = 0,
  5.     _recursions   = 0; // 重入次数
  6.     _object       = NULL;
  7.     _owner        = NULL; // 持有锁的线程
  8.     _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
  9.     _WaitSetLock  = 0 ;
  10.     _Responsible  = NULL ;
  11.     _succ         = NULL ;
  12.     _cxq          = NULL ; // 多个线程争抢锁,会先存入这个单向链表
  13.     FreeNext      = NULL ;
  14.     _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
  15.     _SpinFreq     = 0 ;
  16.     _SpinClock    = 0 ;
  17.     OwnerIsThread = 0 ;
  18.   }
复制代码

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

下篇再讲一下Synchronized锁优化的过程。
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4