synchronized 的底层原理

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

推荐:体系化学习Java(Java面试专题)

一、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");
    }
}

在这里插入图片描述
在上面的输出中,我们可以看到对象头中的第一个字节是 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);
    }
}


在这里插入图片描述
上面的例子,对象头的 Mark Word 已经替换为指向锁记录的指针,已经升级成轻量级锁了。

3、重量级锁

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

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

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

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
`synchronized` 关键字的底层实现和锁的获取与释放过程涉及到了 Java 虚拟机(JVM)、Java 对象头和操作系统等多个层面。 当一个线程访问一个被 `synchronized` 修饰的方法或代码块时,JVM 会首先尝试获取这个同步锁。如果这个锁没有被其他线程占用,则该线程可以顺利获取锁,并进入临界区。如果这个锁已经被其他线程占用,则该线程会进入阻塞状态,直到获取到锁为止。 在 Java 对象头中,有一个字段用于存储这个对象的 Mark Word,这个字段中的一部分用于存储锁的信息,例如锁的状态、持有锁的线程 ID 等。当一个线程获取到锁时,会将这个锁的状态设置为“锁定”,同时将持有锁的线程 ID 记录在 Mark Word 中。当这个线程释放锁时,会将锁的状态设置为“未锁定”,并清除持有锁的线程 ID。 在操作系统层面,锁的获取和释放需要通过操作系统提供的原语来实现,例如 Linux 中的 futex。当线程在获取锁时,如果锁已经被其他线程占用,则它会进入阻塞状态。操作系统会将这个线程加入到等待队列中,并在锁被释放时通知到这个线程,使其重新竞争锁。当线程释放锁时,操作系统会将等待队列中的一个或多个线程唤醒,并将锁的所有权转移到其中一个线程手中。 总的来说,`synchronized` 关键字的实现涉及到了多个层面的细节,包括 Java 虚拟机、Java 对象头和操作系统等。了解其底层实现原理有助于我们更好地理解和使用锁机制,从而编写出更加高效和健壮的多线程程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

激流丶

感觉小弟写的不错,给点鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值