基于对锁的升级以及优化进行分类——偏向锁和轻量锁和重量锁

偏向锁

偏向锁是一种Java虚拟机(JVM)在多线程环境下优化同步性能的锁机制,它适用于大多数时间只有一个线程访问同步代码块的场景。当一个线程访问同步代码块时,JVM会把锁偏向于这个线程,后续该线程在进入和退出同步代码块时,无需再做任何同步操作,从而大大降低了获取锁和释放锁的开销。偏向锁是Java内存模型中锁的三种状态之一,位于轻量级锁和重量级锁之前

示例场景和示例代码 

  • 适用场景:单线程重复操作
  • 示例场景:系统启动时的配置加载
  • 说明:系统启动时加载全局配置(如数据库连接参数),后续仅监控线程定时检查配置(无竞争)。
public class ConfigLoader {
    private static final Object configLock = new Object();
    private static Map<String, String> config;

    // 系统启动时初始化配置(单线程操作)
    public static void init() {
        synchronized (configLock) {
            config = loadConfigFromDB(); // 耗时操作
        }
    }

    // 监控线程定期读取配置(无竞争)
    public static void refresh() {
        synchronized (configLock) { // 偏向锁生效
            if (isConfigModified()) {
                config = reloadConfig();
            }
        }
    }
}

锁行为

  • init() 首次触发偏向锁(偏向初始化线程)。

  • 后续 refresh() 由同一线程执行,无同步开销。

偏向锁的优缺点: 

  • 优点
  1. 对于没有或很少发生锁竞争的场景,偏向锁可以显著减少锁的获取和释放所带来的性能损耗。
  • 缺点
  1. 额外存储空间:偏向锁会在对象头中存储一个偏向线程ID等相关信息,这部分额外的空间开销虽然较小,但在大规模并发场景下,累积起来也可能成为可观的成本。
  2. 锁升级开销:当一个偏向锁的对象被其他线程访问时,需要进行撤销(revoke)操作,将偏向锁升级为轻量级锁,甚至在更高竞争情况下升级为重量级锁。这个升级过程涉及到CAS操作以及可能的线程挂起和唤醒,会带来一定的性能开销。
  3. 适用场景有限:偏向锁最适合于绝大部分时间只有一个线程访问对象的场景,这样的情况下,偏向锁的开销可以降到最低,有利于提高程序性能。但如果并发程度较高,或者线程切换频繁,偏向锁就可能不如轻量级锁或重量级锁高效。

轻量锁

轻量级锁是一种在Java虚拟机(JVM)中实现的同步机制,主要用于提高多线程环境下锁的性能。它不像传统的重量级锁那样,每次获取或释放锁都需要操作系统级别的互斥操作,而是尽量在用户态完成锁的获取与释放,避免了频繁的线程阻塞和唤醒带来的开销。轻量级锁的作用主要是减少线程上下文切换的开销,通过自旋(spin-wait)的方式让线程在一段时间内等待锁的释放,而不是立即挂起线程,这样在锁竞争不是很激烈的情况下,能够快速获得锁,提高程序的响应速度和并发性能

在Java中,轻量级锁主要作为JVM锁状态的一种,它介于偏向锁和重量级锁之间。当JVM发现偏向锁不再适用(即锁的竞争不再局限于单个线程)时,会将锁升级为轻量级锁。

轻量级锁适用于同步代码块执行速度快、线程持有锁的时间较短且锁竞争不激烈的场景,如短期内只有一个或少数几个线程竞争同一线程资源的情况

在Java中,轻量级锁的具体实现体现在 java.util.concurrent.locks 包中的 Lock 接口的一个具体实现:java.util.concurrent.locks.ReentrantLock,它支持可配置为公平或非公平模式的轻量级锁机制,当使用默认构造函数时,默认是非公平锁(类似于轻量级锁的非公平性质)。不过,JVM的内置 synchronized 关键字在JDK 1.6之后引入了锁升级机制,也包含了偏向锁和轻量级锁的优化。

示例场景和示例代码

  • 适用场景:低竞争短任务
  • 案例:多线程更新独立计数器
  • 说明:每个线程更新专属计数器(如用户请求计数),偶尔发生锁竞争。
public class LightweightLockExample {
    private static class Counter {
        private int count = 0;
        private final Object lock = new Object();

        public void increment() {
            synchronized (lock) { // 轻量级锁
                count++; // 快速操作
            }
        }
    }

    public static void main(String[] args) {
        Counter[] counters = new Counter[4];
        for (int i = 0; i < counters.length; i++) {
            counters[i] = new Counter();
        }

        // 模拟低竞争:多个线程操作不同计数器
        for (int i = 0; i < 100; i++) {
            int idx = i % counters.length;
            new Thread(() -> counters[idx].increment()).start();
        }
    }
}

锁行为

  • 线程操作独立计数器时,无竞争(轻量级锁直接生效)。

  • 偶尔哈希冲突导致竞争时,通过自旋快速获取锁

轻量锁的优缺点:

  • 优点
  1. 低开销:轻量级锁通过CAS操作尝试获取锁,避免了重量级锁中涉及的线程挂起和恢复等高昂开销。
  2. 快速响应:在无锁竞争或者锁竞争不激烈的情况下,轻量级锁使得线程可以迅速获取锁并执行同步代码块。
  • 缺点
  1. 自旋消耗:当锁竞争激烈时,线程可能会长时间自旋等待锁,这会消耗CPU资源,导致性能下降。
  2. 升级开销:如果自旋等待超过一定阈值或者锁竞争加剧,轻量级锁会升级为重量级锁,这个升级过程本身也有一定的开销。

重量锁

重量级锁是指在多线程编程中,为了保护共享资源而采取的一种较为传统的互斥同步机制,通常涉及到操作系统的互斥量(Mutex)或者监视器锁(Monitor)。在Java中,通过synchronized关键字实现的锁机制在默认情况下就是重量级锁。确保任何时刻只有一个线程能够访问被锁定的资源或代码块,防止数据竞争和不一致。保证了线程间的协同工作,确保在并发环境下执行的线程按照预定的顺序或条件进行操作。

在Java中,重量级锁主要指的是由 synchronized 关键字实现的锁,它在JVM内部由Monitor实现,属于内建的锁机制。另外,java.util.concurrent.locks 包下的 ReentrantLock 等类也可实现重量级锁,这些锁可以根据需要调整为公平锁或非公平锁。

示例场景和示例代码

  • 适用场景:高竞争长任务
  • 案例:订单支付系统
  • 说明:多线程同时处理支付订单(竞争激烈),且支付流程包含网络IO(长耗时操作)。
public class PaymentSystem {
    private final Object paymentLock = new Object();

    public void processPayment(Order order) {
        synchronized (paymentLock) { // 重量级锁
            validateOrder(order);     // 校验订单
            deductInventory(order);   // 扣减库存(耗时)
            callPaymentGateway(order); // 调用支付接口(网络IO)
            updateOrderStatus(order); // 更新状态
        }
    }

    // 模拟高并发支付
    public static void main(String[] args) {
        PaymentSystem system = new PaymentSystem();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> system.processPayment(new Order())).start();
        }
    }
}

锁行为

  • 多线程激烈竞争锁时,升级为重量级锁。

  • 未抢到锁的线程被操作系统挂起(避免CPU空转)。

重量级锁的优缺点

  • 优点
  1. 强一致性:重量级锁提供了最强的线程安全性,确保在多线程环境下数据的完整性和一致性。
  2. 简单易用:synchronized关键字的使用简洁明了,不易出错。
  • 缺点
  1. 性能开销大:获取和释放重量级锁时需要操作系统介入,可能涉及线程的挂起和唤醒,造成上下文切换,这对于频繁锁竞争的场景来说性能代价较高。
  2. 延迟较高:线程获取不到锁时会被阻塞,导致等待时间增加,进而影响系统响应速度。

三种锁的对比总结:

场景锁类型触发条件性能影响
单线程重复操作偏向锁同一线程多次访问无同步开销
低竞争短任务轻量级锁多线程交替竞争(自旋成功)少量CPU自旋消耗
高竞争或长任务重量级锁自旋失败/竞争激烈线程挂起唤醒开销大

注: 

  • 从JDK 15开始,偏向锁默认禁用(可通过 -XX:+UseBiasedLocking 手动启用)。

  • 轻量级锁在竞争加剧时会升级为重量级锁(JVM自动管理)。

  • 长任务建议用 ReentrantLock 替代 synchronized(支持超时和公平锁)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小黄工程师学习进阶版

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值