Synchronized关键字

Synchronized 简介

在某些多线程场景下,如果不进行同步会导致数据不安全,为了解决线程安全的问题,引入了锁的概念。

java中常用的锁有synchronized和lock两种,本文来分析synchronized关键字的原理。

作用:
保证被Synchronized修饰的方法或代码同一时刻最多只有1个线程执行。

Synchronized使用场景:

  • Synchronized修饰普通同步方法:锁对象当前实例对象
  • Synchronized修饰静态同步方法:锁对象是当前的类Class对象
  • Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象(可以是某个对象,也可以是某个类)

Synchronized实现原理

Java对象头

java对象由如下几部分组成:

  • 对象头
    • Mark Word:包含一系列的标记位比如hashcode、GC分代年龄、偏向锁位,锁标志位等,而且在对象被加了不同量级的锁时所包含的内容和布局都有所不同
    • Klass Pointer:是一个指针,指向描述这个对象类型的元对象
  • 实例数据
    • 描述成员变量的信息,如果成员变量是引用类型,那么它就是一个指针
    • 实例数据的大小是所有成员变量的占用空间(基本数据类型大小+指针大小)
  • 对齐填充:java中会将对象大小设定为8bytes的整数倍,如果对象头+实例数据的大小不是8bytes的倍数,那么会在padding区域填充字节使得对象占用空间是8bytes的倍数

Mark Word(对象头)


Synchronized在JVM中的实现原理

Synchronized修饰代码块

Synchronized代码块同步在需要同步的代码块开始的位置插入monitorentry指令,在同步结束的位置或者异常出现的位置插入monitorexit指令。

JVM要保证monitorentry和monitorexit都是成对出现的,任何对象都有一个monitor与之对应,当这个对象的monitor被持有以后,它将处于锁定状态。

同步代码块如下:

public class SyncCodeBlock {
   public int i;
   public void syncTask(){
       synchronized (this){
           i++;
       }
   }
}

对同步代码块编译后的class字节码文件反编:

  public void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  //注意此处,进入同步方法
         4: aload_0
         5: dup
         6: getfield      #2             // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2            // Field i:I
        14: aload_1
        15: monitorexit   //注意此处,退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit //注意此处,退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table:
      //省略其他字节码.......

同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句。
为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行monitorexit语句,因此会出现两句monitorexit语句。

Synchronized修饰方法

Synchronized方法同步是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。

同步方法代码如下:


public class SyncMethod {
   public int i;
   public synchronized void syncTask(){
           i++;
   }
}

对同步方法编译后的class字节码反编译:

public synchronized void syncTask();
    descriptor: ()V
    //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10
}

方法开始和结束的地方都没有出现monitorentry和monitorexit指令,但是出现的ACC_SYNCHRONIZED标志位。

posted @ 2022-03-13 00:05  当康  阅读(64)  评论(0编辑  收藏  举报