synchronized 的底层原理

简介: synchronized 的底层是通过 Java 中的监视器锁(monitor)来实现的。每个 Java 对象都有一个与之对应的监视器锁,当一个线程获取了该对象的监视器锁,就可以执行 synchronized 代码块或方法。其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。

tip: 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。

一、synchronized 的底层原理

synchronized 的底层是通过 Java 中的监视器锁(monitor)来实现的。每个 Java 对象都有一个与之对应的监视器锁,当一个线程获取了该对象的监视器锁,就可以执行 synchronized 代码块或方法。其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。

在 Java 中,每个对象都有一个与之关联的管程(monitor)。管程包括两个部分:一个是对象头(Object Header),用于存储对象的元数据信息;另一个是监视器锁(Monitor Lock),用于实现线程同步。当一个线程获取了对象的监视器锁,就可以执行 synchronized 代码块或方法,其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。

当一个线程进入 synchronized 代码块或方法时,它会尝试获取对象的监视器锁。如果该锁没有被其他线程占用,则该线程获取锁并继续执行 synchronized 代码块或方法;如果该锁已经被其他线程占用,则该线程会进入锁池(Lock Pool)等待,直到该锁被其他线程释放。

当一个线程释放对象的监视器锁时,它会唤醒锁池中的一个等待线程,让其获取锁并继续执行 synchronized 代码块或方法。如果锁池中有多个等待线程,那么唤醒哪个线程是不确定的,取决于 JVM 的实现。

二、synchronized 的锁升级原理

synchronized 的锁升级指的是在不同的情况下,synchronized 锁的状态会从偏向锁、轻量级锁、重量级锁等级别逐步升级的过程。在 Java 6 及之前的版本中,synchronized 的锁升级过程是固定的,而在 Java 6 及之后的版本中,锁升级过程是根据当前锁的状态和竞争情况动态调整的。

偏向锁:当一个线程访问同步块并获取锁时,会在对象头中记录锁偏向的线程 ID,以后该线程再次进入同步块时,只需判断当前线程 ID 是否与对象头中记录的线程 ID 相同,如果相同,就可以直接进入同步块,无需进行额外的同步操作。如果有其他线程竞争锁,则偏向锁会被撤销。

轻量级锁:当一个线程获取锁失败时,会尝试使用轻量级锁来提高性能。轻量级锁是通过将对象头中的信息复制到线程的栈帧中,然后在栈帧中进行同步操作来实现的。如果在同步过程中发生竞争,则轻量级锁会升级为重量级锁。

重量级锁:当多个线程竞争同一个锁时,会升级为重量级锁。重量级锁是通过操作系统的互斥量来实现的,每次加锁和释放锁都需要进行系统调用,开销较大。

在 Java 6 及之前的版本中,锁升级过程是固定的,即从偏向锁升级到轻量级锁,再升级到重量级锁。而在 Java 6 及之后的版本中,锁升级过程是根据当前锁的状态和竞争情况动态调整的,可以根据实际情况选择偏向锁、轻量级锁或重量级锁,从而提高程序的性能。

1、偏向锁

偏向锁是 Java 中的一种锁优化机制。它的主要目的是减少无竞争情况下的同步操作,从而提高程序的性能。在偏向锁的情况下,当一个线程访问同步块并获取锁时,会在对象头中记录锁偏向的线程 ID,以后该线程再次进入同步块时,只需判断当前线程 ID 是否与对象头中记录的线程 ID 相同,如果相同,就可以直接进入同步块,无需进行额外的同步操作。

偏向锁的优点是可以减少同步操作的开销,尤其是在无竞争的情况下。在无竞争的情况下,偏向锁可以避免多余的同步操作,从而提高程序的性能。另外,偏向锁的实现比较简单,开销也比较小。

偏向锁的缺点是当有其他线程竞争锁时,偏向锁会被撤销,这样就会导致额外的同步操作,从而降低程序的性能。此外,偏向锁只适用于无竞争的情况,如果存在竞争,就需要使用其他的锁机制来保证线程安全。

需要注意的是,偏向锁只适用于单线程访问同步块的情况。如果多个线程访问同一个同步块,就会出现竞争,此时偏向锁会被撤销,从而降低程序的性能。

package com.pany.camp.lock;

import org.openjdk.jol.info.ClassLayout;

public class BiasedLockDemo {
   

    public static void main(String[] args) throws InterruptedException {
   
        BiasedLockDemo biasedLockDemo = new BiasedLockDemo();
        biasedLockDemo.doSomething();
        System.out.println(ClassLayout.parseInstance(biasedLockDemo).toPrintable());
    }

    public synchronized void doSomething() {
   
        System.out.println("doSomething() is called");
    }
}

image.png

在上面的输出中,我们可以看到对象头中的第一个字节是 01 ,这个字节的二进制表示是 00000001 ,其中第0位是1,表示这个对象的锁是偏向锁。如果第0位是0,表示这个对象的锁不是偏向锁。

可以通过 -xx:UseBiasedLocking 禁用偏向锁。

2、轻量级锁

轻量级锁是Java虚拟机为了提高多线程程序性能而引入的一种锁优化机制。与传统的重量级锁相比,轻量级锁的锁操作使用了更加轻量级的机制,避免了不必要的线程阻塞和上下文切换,从而提高了多线程程序的执行效率。

轻量级锁的实现原理是,当一个线程第一次进入同步代码块时,Java虚拟机会为该对象分配一个锁记录(Lock Record)并将对象头中的Mark Word复制到锁记录中。然后,虚拟机会尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针,从而将对象的锁状态升级为轻量级锁。如果CAS操作成功,该线程就可以继续执行同步代码块,否则就会退化为重量级锁,从而保证线程安全。

当一个线程持有轻量级锁时,其他线程可以使用CAS操作来尝试获取锁。如果获取成功,就可以继续执行同步代码块。如果获取失败,就会进入自旋状态,等待持有锁的线程释放锁。如果自旋次数超过一定阈值,或者持有锁的线程被阻塞,就会退化为重量级锁。

总之,轻量级锁是Java虚拟机为了提高多线程程序性能而引入的一种锁优化机制,它避免了不必要的线程阻塞和上下文切换,从而提高了多线程程序的执行效率。

package com.pany.camp.lock;

import cn.hutool.core.thread.ThreadUtil;
import org.openjdk.jol.info.ClassLayout;

public class LightLock {
   
    private int count = 0;

    public synchronized void increment() {
   
        count++;
    }

    public static void main(String[] args) {
   
        final LightLock obj = new LightLock();
        final int numThreads = 5;
        final int numIterations = 10000;

        Thread t1 = new Thread(() -> {
   
            ThreadUtil.sleep(1000);
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }, "t1");
        t1.start();

        // 创建多个线程并启动
        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
   
            threads[i] = new Thread(() -> {
   
                for (int j = 0; j < numIterations; j++) {
   
                    // 对共享对象加锁
                    synchronized (obj) {
   
                        obj.increment();
                        ThreadUtil.sleep(1);
                    }
                }
            });
            threads[i].start();
        }


        System.out.println("Count: " + obj.count);
    }
}

image.png

上面的例子,对象头的 Mark Word 已经替换为指向锁记录的指针,已经升级成轻量级锁了。

3、重量级锁

重量级锁是Java中的一种锁机制,也称为互斥锁。它是基于操作系统的互斥量实现的,需要进行用户态与内核态之间的切换,因此开销比较大,效率较低。当多个线程竞争同一个锁时,如果该锁是重量级锁,那么线程会进入阻塞状态,等待锁的释放。在Java中,synchronized关键字使用的锁就是重量级锁。

重量级锁的实现原理是,在Java虚拟机中,每个对象都与一个监视器关联,当一个线程需要获取该对象的锁时,它会尝试获取该对象的监视器锁。如果该锁已经被其他线程持有,那么该线程就会进入阻塞状态,等待锁的释放。当该锁被释放时,等待队列中的线程会被唤醒,并重新尝试获取锁。

重量级锁的优点是实现简单,可靠性高,不会出现死锁等问题。缺点是由于需要进行用户态与内核态之间的切换,因此开销比较大,效率较低。在多线程环境下,如果使用重量级锁过多,会导致系统性能下降,因此应该根据具体情况选择合适的锁机制。

目录
相关文章
|
4月前
|
Java
【多线程系列】你先说说synchronized的实现原理
面试官:听说你精通多线程,那我就考考你吧面试官:不用慌尽管说,错了也没关系😊。。。❤️。
【多线程系列】你先说说synchronized的实现原理
|
4月前
|
Java 编译器
synchronized原理
synchronized原理
|
4月前
|
存储 监控 安全
Synchronized 实现原理
Synchronized 实现原理
55 0
|
4月前
|
安全 Java
Java并发编程:Synchronized及其实现原理
Java并发编程:Synchronized及其实现原理
47 4
|
19天前
|
缓存 Java 编译器
JAVA并发编程synchronized全能王的原理
本文详细介绍了Java并发编程中的三大特性:原子性、可见性和有序性,并探讨了多线程环境下可能出现的安全问题。文章通过示例解释了指令重排、可见性及原子性问题,并介绍了`synchronized`如何全面解决这些问题。最后,通过一个多窗口售票示例展示了`synchronized`的具体应用。
|
2月前
|
存储 安全 Java
(二) 彻底理解Java并发编程之 Synchronized关键字实现原理剖析
Synchronized 关键字(互斥锁)原理,一线大厂不变的面试题,同时也是理解 Java 并发编程必不可少的一环!其中覆盖的知识面很多,需要理解的点也很多,本文会以相关书籍和结合自己的个人理解,从基础的应用范围到底层深入剖析的方式进行阐述,如果错误或疑问欢迎各位看官评论区留言纠正,谢谢!
|
3月前
|
存储 Java C++
Synchronized底层原理
Synchronized底层原理
|
3月前
|
存储 Java
Java并发编程 Synchronized原理
Java并发编程 Synchronized原理
23 0
|
Java API 调度
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别
73 0
|
存储 设计模式 缓存
synchronized原理剖析
synchronized原理剖析