导读:本期聚焦于小伙伴创作的《怎么利用 LockSupport 构建一个比 synchronized 更灵活的自旋-阻塞组合锁》,敬请观看详情。synchronized 在竞争激烈时只能盲目阻塞,无法根据场景切换策略。LockSupport 提供了 park 与 unpark 的精确线程调度能力,可以先将短临界区用自旋尝试获取,失败后再 park 挂起,避免无谓的上下文切换。相比内置锁,组合锁允许设置自旋次数、支持超时与可中断,也能在锁释放时定向唤醒指定线程。本文从底层原理讲清如何用 Unsafe 暴露的 LockSupport 方法拼装出一套可控的混合锁,并给出完整的 Java 实现与压测对比,帮助你在高并发系统中按实际负载灵活取舍自旋与阻塞。

在 Java 并发编程里,synchronized 是最常用的内置锁,但它策略单一:拿不到锁就直接挂起线程,等待操作系统调度。这种纯粹阻塞的方式在锁持有时间极短的场景下会带来不必要的上下文切换开销。LockSupport 作为 JDK 提供的底层线程阻塞工具,能够让我们手动控制“先自旋尝试、失败再阻塞”的混合逻辑,从而构建出比 synchronized 更灵活的锁。

怎么利用 LockSupport 构建一个比 synchronized 更灵活的自旋-阻塞组合锁

一、为什么需要自旋-阻塞组合锁

自旋锁的优点是响应快,线程拿不到锁时不会立刻放弃 CPU,而是循环重试,适合临界区非常短的场景。但缺点也明显:如果锁被长时间持有,自旋会白白浪费处理器资源。阻塞锁则相反,拿不到锁就 park 休眠,不占 CPU,但唤醒需要内核介入,延迟较高。

组合锁的思路是:先让线程自旋有限次数,若期间锁释放则立刻获取;若超过阈值仍失败,再调用 LockSupport.park 挂起。这样既保留了短临界区的高吞吐,又避免了长竞争下的 CPU 空转。synchronized 无法调节这种策略,而基于 LockSupport 我们可以完全自定义。

二、LockSupport 核心方法解析

LockSupport 通过 Unsafe 类实现,核心是两个静态方法:park 和 unpark。park 会阻塞当前线程,直到其他线程调用 unpark 并传入该线程对象,或者线程被中断、超时。unpark 则可以提前“许可”某个线程,使其后续 park 不阻塞(许可可累积一次)。

与 Object.wait/notify 不同,LockSupport 不需要先持有监视器锁,且 unpark 可以在 park 之前调用,避免竞态丢失信号。这种特性非常适合用来实现自定义锁的等待队列和精准唤醒。

import java.util.concurrent.locks.LockSupport;

public class ParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("子线程准备 park");
            LockSupport.park(); // 阻塞直到被 unpark
            System.out.println("子线程被唤醒");
        });
        t.start();
        Thread.sleep(1000);
        LockSupport.unpark(t); // 主线程唤醒子线程
    }
}

三、组合锁的完整实现

下面给出一个简单的自旋-阻塞组合锁。使用 AtomicReference 保存持有线程,state 表示是否加锁。tryLock 时先自旋指定次数,失败则将当前线程放入等待队列并 park。

解锁时,若等待队列有线程,则 unpark 下一个。注意解锁必须用 volatile 或原子类保证可见性,且 unpark 要在清除锁状态后执行,防止唤醒的线程看不到最新状态。

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class HybridLock {
    private final AtomicReference<Thread> owner = new AtomicReference<>();
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
    private final int spinCount; // 自旋次数阈值

    public HybridLock(int spinCount) {
        this.spinCount = spinCount;
    }

    public void lock() {
        Thread current = Thread.currentThread();
        // 先自旋尝试
        for (int i = 0; i < spinCount; i++) {
            if (owner.compareAndSet(null, current)) {
                return;
            }
        }
        // 自旋失败,进入阻塞
        waiters.add(current);
        while (!owner.compareAndSet(null, current)) {
            LockSupport.park(); // 挂起,等待 unpark
        }
        waiters.remove(current);
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        if (owner.get() != current) {
            throw new IllegalMonitorStateException();
        }
        owner.set(null); // 释放锁
        Thread next = waiters.peek();
        if (next != null) {
            LockSupport.unpark(next); // 唤醒队首线程
        }
    }
}

四、与 synchronized 的对比及使用建议

在压测中,若临界区仅做加减法,自旋次数设为 100 的组合锁吞吐量通常高于 synchronized;但若临界区包含 IO 或睡眠,synchronized 的纯阻塞反而更省资源。组合锁的灵活性在于参数可调,也能支持超时加锁、可中断等 synchronized 不具备的能力。

使用组合锁时要注意:自旋次数需根据 CPU 核数和临界区耗时评估;等待队列若为自定义链表需处理伪唤醒;生产环境可借助 AQS 框架简化,但理解 LockSupport 底层有助于排查死锁与性能瓶颈。总体而言,当你需要对锁策略精细调控时,基于 LockSupport 的组合锁是比 synchronized 更优的备选方案。

LockSupport自旋锁阻塞锁修改时间:2026-08-05 08:39:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。