Synchronized同步锁编译代码分析

0. 前言

synchronized关键字作为Java常用的同步锁机制,是最简单的解决并发问题的一种方法,它具备以下特点:

1.原子性:确保线程互斥的访问同步代码;
2.可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
3.有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;

1. Synchronized 的使用

从语法上讲,Synchronized可以把任何一个非null对象作为"锁",在HotSpot JVM实现中,锁有个专门的名字:对象监视器(Object Monitor)。

Synchronized总共有三种用法:

1.当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
2.当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
3.当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;

测试代码:

public class SynchronizesTest {
    static int w = 1;
    public static void main(String[] arg0){
        synchronized (SynchronizesTest.class){
            w = 2;
        }
    }

    public synchronized void syncMethod(){
        w = 3;
    }

    private void syncThisMethod(){
        w = 4;
        synchronized (this){
            w = 5;
        }
    }

    int syncObjectMethod(){
        int i = 6;
        synchronized (new Object()){
            i = 7;
        }
        return 0;
    }
}
  • 反编译上面的类:javap -v SynchronizesTest.class
    E:\workspace\...\app\src\main\java\com\linx\>javap -v  SynchronizesTest.class
    Classfile /E:/workspace/.../app/src/main/java/com/linx/SynchronizesTest.class
    Last modified 2019-5-17; size 872 bytes
     MD5 checksum 852f9911b0a44e910f2c0b2c14dc0440
    Compiled from "SynchronizesTest.java"
    public class com.linx.SynchronizesTest
    minor version: 0
    major version: 52
    flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
    ...
    {
    static int w;
    descriptor: I
    flags: ACC_STATIC
    
    public com.linx.SynchronizesTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
    
    public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class com/linx/SynchronizesTest
         2: dup
         3: astore_1
         4: monitorenter
         5: iconst_2
         6: putstatic     #3                  // Field w:I
         9: aload_1
        10: monitorexit
        11: goto          19
        14: astore_2
        15: aload_1
        16: monitorexit
        17: aload_2
        18: athrow
        19: return
      Exception table:
         from    to  target type
             5    11    14   any
            14    17    14   any
      LineNumberTable:
        line 6: 0
        line 7: 5
        line 8: 9
        line 9: 19
      StackMapTable: ...
      ...
    
    public synchronized void syncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_3
         1: putstatic     #3                  // Field w:I
         4: return
      LineNumberTable:
        line 12: 0
        line 13: 4
    
    int syncObjectMethod();
    descriptor: ()I
    flags:
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        6
         2: istore_1
         3: new           #4                  // class java/lang/Object
         6: dup
         7: invokespecial #1                  // Method java/lang/Object."<init>":()V
        10: dup
        11: astore_2
        12: monitorenter
        13: bipush        7
        15: istore_1
        16: aload_2
        17: monitorexit
        18: goto          26
        21: astore_3
        22: aload_2
        23: monitorexit
        24: aload_3
        25: athrow
        26: iconst_0
        27: ireturn
      Exception table:
         from    to  target type
            13    18    21   any
            21    24    21   any
    ...
    }
    SourceFile: "SynchronizesTest.java"
    
  1. monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1.如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
    2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

  2. monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

    monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;

  3. ACC_SYNCHRONIZED:作为同步方法的标志。作用原理也是monitor

    当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

2. 监视器(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对象的起始地址。

3. 总结

  • synchronized作为常用的同步锁,它与其他的锁机制相同,都是通过CAS竞争获取锁对象
  1. synchronized方法,monitor是这个对象本身,仅仅同步引用这个对象的线程
  2. synchronized(object),monitor是参数对象
  3. synchronised(Clazz.class),monitor是方法区中这个类的模板Class,锁具备唯一性
  4. 注意:一般要求所具备唯一性,所以monitor一般是静态常量;
  • 更多的了解:自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、锁粗化、锁消除等,大家自行学习。高并发场景对同步锁要求很高,效率与安全怎么平衡…
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值