synchronized关键字总结

synchronized关键字总结

薛定谔的汪

在并发编程中,synchronized 关键字非常常见,在 JDK 源码中也有很多,典型基础的类有 StringBuffer,我们知道 StringBuilder 是非线程安全的,StringBuffer 是线程安全的,就是因为 StringBuffer 源码的一些方法上使用了synchronized 关键字来保证同步。

synchronized关键字原理

synchronized 代码块同步

1
2
3
4
5
6
7
8
public class SynchronizedTest {

public static void main(String[] args) {
synchronized (SynchronizedTest.class){
System.out.println("hah");
}
}
}

上面SynchronizedTest类的一段代码块用 synchronized 修饰,我们使用 javac SynchronizedTest.java 命令将其进行编译,然后使用javap -c SynchronizedTest.class 反编译其 class 文件观察输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Compiled from "SynchronizedTest.java"
public class SynchronizedTest {
public SynchronizedTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #2 // class SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String hah
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}

看到有monitorentermonitorexit两个指令,synchronized 的原理就是使用对象监视器来实现对方法和代码块的同步的:在进入同步方法前加一个 monitorenter 指令,在退出、异常时加入 monitorexit指令,其本质是对象监视器(monitor)的获取,这个获取具有排他性,所以同一时刻只能有同一个线程获到 monitor,获取到 monitor 的执行代码块、执行完后退出释放 monitor,未获得 monitor 的线程会再次去尝试获得 monitor,最终实现同步的目的。

synchronized 代码块实现细节:

对于 monitorenter,如果 monitor 的进入数为0,则该线程获取锁,并设置为1,再次重入时对其+1,解锁-1,最终释放后等于0。如果 monitor 不为0(大于0),则表示该锁被其他线程占用,则等待 monitor等于0时才获取锁。monitorexit 与之过程相反。

synchronized方法同步

方法同步和代码块同步的实现细节不太一样,上述例子代码块同步是使用 monitorentermonitorexit 指令来实现,而方法同步是用另一种方式来实现的,JVM 根据 ACC_SYNCHRONIZED 标识符来实现方法的同步,当方法调用时,调用指令会检查方法的 ACC_SYNCHRONIZED 标识符是否被设置,如果设置了,线程将获取对象监视器 monitor,获取成功后去执行代码块,执行完后再释放 monitor,方法执行期间,其他线程都无法再获得同一个monitor 对象,实现了方法的同步。

synchronized 实现同步的基础

任何对象都可以作为锁,也就是上述的 monitor,具体表现为:

对于普通同步方法,锁的是当前实例对象;

对于静态同步方法,锁的是 Class 对象;

对于同步代码块,锁的是小括号里的对象;

当一个线程访问同步方法、同步代码块时,必须要先获得锁,退出或抛出异常时必须释放锁。

synchronized 关键字优化

synchronized 一直被称为重量级锁,其实在jdk1.6对其做了优化、引入了轻量锁和偏量锁。

关于轻量锁和偏量锁更形象的描述请参照这篇短文 https://www.jianshu.com/p/afa5296a4832

随着 synchronize 关键字不断优化,jdk8的 ConcurrentHashMap 抛弃了 ReentrantLock 而使用 synchronize。

  • Title: synchronized关键字总结
  • Author: 薛定谔的汪
  • Created at : 2018-07-31 18:01:54
  • Updated at : 2023-11-17 19:37:37
  • Link: https://www.zhengyk.cn/2018/07/31/java/synchronized/
  • License: This work is licensed under CC BY-NC-SA 4.0.