• Synchronized锁详解


    在Java中,synchronized锁可能是我们最早接触的锁了,在 JDK1.5之前synchronized是一个重量级锁,相对于juc包中的Lock,synchronized显得比较笨重

    庆幸的是在 Java 6 之后 Java 官⽅对从 JVM 层⾯对synchronized进行⼤优化,所以现在的 synchronized 锁效率也优化得很不错。

    为什么呢?

    因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

    庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,后续都有细节说明。
     

    1. synchronized的类别

    synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁。

    synchronized 关键字加到实例方法上是给对象实例上锁。

    小结:

    经典代码示例:

    1. public class Singleton {
    2. //保证有序性,防止指令重排
    3. private volatile static Singleton uniqueInstance;
    4. private Singleton() {
    5. }
    6. public static Singleton getUniqueInstance() {
    7. //先判断对象是否已经实例过,没有实例化过才进入加锁代码
    8. if (uniqueInstance == null) {
    9. //类对象加锁
    10. synchronized (Singleton.class) {
    11. if (uniqueInstance == null) {
    12. uniqueInstance = new Singleton();
    13. }
    14. }
    15. }
    16. return uniqueInstance;
    17. }
    18. }

    2. synchronized作用

    • (1)、原子性所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。synchronized修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放。
    • (2)、可见性:**可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。 **synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到共享内存当中,保证资源变量的可见性。
    • (3)、有序性有序性值程序执行的顺序按照代码先后执行。 synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。

    3. synchronized底层实现原理

    synchronized底层实现主要依赖java对象头和monitor:

    java对象头

    java实例对象存储在Hotspot虚拟中主要包括三个模块,如下图:

    这里主要讲解一下对象头的组成,如下图:

    对象头中,涉及锁部分的markWord我们这里着重讲解一下,构成如下图:

     

    监视器(Monitor)

    任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。

    1. MonitorEnter指令:插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象Monitor的所有权,即尝试获得该对象的锁;
    2. MonitorExit指令:插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit;

    那什么是Monitor?可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。

    与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁

    也就是通常说Synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的。

    JVM底层原理

    synchronized同步代码块的情况

    1. public void method(){
    2. synchronized(this){
    3. System.out.println("代码块solo");
    4. }
    5. }

    通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行javap -c -s -v -l SynchronizedDemo.class。

    image.png

    从上面我们可以看出:

    synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

    当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

    在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对象中都内置了一个 ObjectMonitor对象。

    另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因(抛出该异常,表示一个线程已经尝试等待一个对象的监视器,或者通知正在等待一个对象的监视器的其他线程,而没有拥有指定的监视器)。

    1. public static void main(String[] args) {
    2. Object o = new Object();
    3. try {
    4. o.wait();
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. o.notify();
    9. }
    10. //Exception in thread "main" java.lang.IllegalMonitorStateException
    11. //at java.lang.Object.wait(Native Method)
    12. //at java.lang.Object.wait(Object.java:502)
    13. //at com.pingfa.two.lock.Test.main(Test.java:15)


    synchronized 必须是进入同一个对象的monitor才有上述的效果。每一个对象会有一个monitor,不加synchronized的对象,不会关联监视器,不遵从以上规则。

    在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

    在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

    synchronized同步方法的情况

    1. public class SynchronizedDemo2 {
    2.     public synchronized void method() {
    3.         System.out.println("synchronized 方法");
    4.     }
    5. }


    synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

    总结:
    synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

    synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

    不过两者的本质都是对对象监视器 monitor 的获取。

    monitor的结构:

    image.png

    刚开始 Monitor 中 Owner 为 null
    当 Thread-2 执行 synchronized(obj) 就会将Monitor的所有者 Owner 置为Thread-2 ,Monitor中只能有一个Owner
    在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED
    Thread-2执行完同步代码块中的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争时是非公平的
    图中WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,在wait-notify中
    synchronized 必须是进入同一个对象的monitor才有上述的效果。每一个对象会有一个monitor,不加synchronized的对象,不会关联监视器,不遵从以上规则。

    4. synchronized锁状态及升级过程

     

     

    5. 额外补充

     hotspot对锁进行升级,不仅可以锁消除,还可以锁粗化:

    参考文章:

    synchronized详解_万里长江雪的博客-CSDN博客_synchronized

    synchronized详解 - 三分恶 - 博客园

  • 相关阅读:
    pdf文件怎么转化为word,pdf转换成word的方法
    盘点一个pandas.merge的问题
    【算法】位运算算法——消失的两个数字(困难)
    C++模板初阶
    4.3 x64dbg 搜索内存可利用指令
    20220721 积分环节的时频域分析
    2023兰州理工大学计算机考研信息汇总
    【电力系统】基于两阶段鲁棒优化算法的微网多电源容量配置附matlab代码
    Linux友好度太低?试试Clion远程开发|ssh连接远程主机
    Redis-使用java代码操作Redis
  • 原文地址:https://blog.csdn.net/lixiangchibang/article/details/126085373