线程安全synchronized
简单使用
https://www.baeldung.com/java-synchronized
可重入性
可重入锁是同一个线程重复请求由自己持有的锁对象时,可以请求成功而不会发生死锁。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。
public class SynchronizedTest {
static synchronized void parentMethod() {
childMethod();
System.out.println("parent");
}
static synchronized void childMethod() {
System.out.println("child method");
}
public static void main(String[] args) {
new Thread(SynchronizedTest::parentMethod).start();
}
}
输出结果:
child method
parent
可重入原理
synchronized通过monitor计数器实现,当执行monitorenter命令时:判断当前monitor计数器值是否为0,如果为0,则说明当前线程可直接获取当前锁对象;否则,判断当前线程是否和获取锁对象线程是同一个线程。若是同一个线程,则monitor计数器累加1,当前线程能再次获取到锁;若不是同一个线程,则只能等待其它线程释放锁资源。当执行完synchronized锁对象的代码后,就会执行monitorexit命令,此时monitor计数器就减1,直至monitor计数器为0时,说明锁被释放了。
异常会释放锁
public class SynchronizedTest {
static synchronized void method1() {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Start to throw exception, time: " + LocalDateTime.now());
throw new RuntimeException();
}
}
static synchronized void method2() {
System.out.println("Method 2, time: " + LocalDateTime.now());
}
public static void main(String[] args) throws InterruptedException {
new Thread(SynchronizedTest::method1).start();
TimeUnit.SECONDS.sleep(2);
new Thread(SynchronizedTest::method2).start();
}
}
输出结果:
Start to throw exception, time: 2023-09-01T10:06:06.560455
Method 2, time: 2023-09-01T10:06:06.561578
Exception in thread "Thread-0" java.lang.RuntimeException
at com.huhx.thread.SynchronizedTest.method1(SynchronizedTest.java:31)
at java.base/java.lang.Thread.run(Thread.java:833)
锁升级
为了提升性能,JDK1.6 引入了偏向锁、轻量级锁、重量级锁概念,来减少锁竞争带来的上下文切换,而正是新增的Java对象头实现了锁升级功能。
当 Java 对象被 Synchronized 关键字修饰成为同步锁后,围绕这个锁的一系列升级操作都将和 Java 对象头有关。
在 JDK1.6 JVM 中,对象实例在堆内存中被分为了三个部分:对象头、实例数据和对齐填充。其中 Java 对象头由 Mark Word、指向类的指针以及数组长度三部分组成。
Mark Word 记录了对象和锁有关的信息。Mark Word 在 64 位 JVM 中的长度是 64bit,我们可以一起看下 64 位 JVM 的存储结构是怎么样的。如下图所示:

锁升级功能主要依赖于 Mark Word 中的锁标志位和释放偏向锁标志位,Synchronized 同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。