Skip to main content

线程安全Synchronized

huhxAbout 2 minjavaThreadConcurrency

简单使用

https://www.baeldung.com/java-synchronizedopen in new window

可重入性

可重入锁是同一个线程重复请求由自己持有的锁对象时,可以请求成功而不会发生死锁。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。

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 的存储结构是怎么样的。如下图所示:

image 0
image 0

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

FAQ

总结

参考