一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情
synchronized 使用方式
Java 中的每个对象都可以作为锁,具体表现为以下三种形式:
- 修饰普通方法,锁是当前实例对象;
- 修饰静态同步方法,锁是当前类的 Class 对象
- 修饰代码块,锁是 synchronized 括号里配置的对象;
下面通过举例看一下这几种加锁方式
同步方法
public synchronized void addI
同步方法上锁,使用命令将 javap -c -v FileName.class 文件转换成字节码指令后,
核心结果如下:
public void addII(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
.... 省略
public synchronized void addI(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, java
Code:
stack=3, locals=3, args_size=2
复制代码
可以看到同步方法块上有 flag标记ACC_SYNCHRONIZED,表明此方法是同步的。非同步方法flags并没有ACC_SYNCHRONIZED标记。
静态方法同步
public class InternalVariable {
public static void addIII(String username){
// ... 代码省略
}
}
复制代码
反编译查看核心编译结果:
public static synchronized void addIII(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=3, args_size=1
复制代码
与普通方法相比,静态方法在 flags 标志位上多了一个 ACC_STATIC。
同步代码块
public class InternalVariable {
public void addIIII(String username) {
synchronized (this) {
// ... 代码
}
}
}
复制代码
反编译结果如下:
public void addIIII(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=6, args_size=2
0: aload_0
1: dup
2: astore_2
3: monitorenter
// ... 省略
135: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
138: aload_2
139: monitorexit
140: goto 150
143: astore 5
145: aload_2
146: monitorexit
147: aload 5
149: athrow
150: return
复制代码
同步代码块有1个monitorenter,但是却有2个monitorexit,这是因为JVM为了保证方法在出异常时也能正常释放锁,编译器会自动产生一个异常处理器, 目的就是用来执行monitorexit指令
synchronized 底层实现
synchronized 底层实现依赖于 JVM,在 JVM 中同步的实现是通过监视器锁 monitorenter 和 monitorexit 指令实现,或者隐式地通过方法调用 (检测到同步标志位)和返回指令实现。这两种,前者对应同步代码块,后者对应同步方法、静态同步方法。
所以同步方法最终还是通过监视器锁来完成线程同步,并没有违背 JVM 中同步是通过 monitor 的进入和退出实现的。