LockSupport 详解及源码分析

LockSupport 详解及源码分析

一、LockSupport 概述

LockSupport 是 Java 并发包(java.util.concurrent.locks)中的核心工具类,提供线程阻塞和唤醒的基本能力。与传统的 wait/notify 机制相比,LockSupport 具有以下优势:

  1. 无需同步块:可在任意位置调用
  2. 更精准控制:以线程为操作对象
  3. 许可机制:支持先唤醒后阻塞
  4. 中断处理:响应中断但不抛出异常

LockSupport 是构建高级同步器(如 ReentrantLock、CountDownLatch 等)的基础。

二、核心方法与功能

方法签名功能描述
park()阻塞当前线程
park(Object blocker)阻塞当前线程,设置阻塞对象(用于诊断)
parkNanos(long nanos)阻塞指定纳秒数
parkUntil(long deadline)阻塞直到指定时间戳
unpark(Thread thread)唤醒指定线程

三、许可机制(Permit)

LockSupport 的核心是 许可机制

  • 每个线程关联一个 二元许可(0 或 1)
  • unpark:将许可设为 1(若已是 1 则保持)
  • park:消耗许可(设为 0)并立即返回(若许可为 1)
  • 若许可为 0,则阻塞线程

关键特性:

  • unparkpark 不会阻塞
  • 多次 unpark 效果等同于一次
  • 许可不累积(最大为 1)

四、源码分析(基于 OpenJDK 17)

1. Unsafe 操作入口

public class LockSupport {
    // 获取 Unsafe 实例
    private static final Unsafe U = Unsafe.getUnsafe();
    
    // Thread 类中 parkBlocker 字段的偏移量
    private static final long PARKBLOCKER;
    
    static {
        try {
            PARKBLOCKER = U.objectFieldOffset
                (Thread.class.getDeclaredField("parkBlocker"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }
    
    private LockSupport() {} // 私有构造,无法实例化
}

2. park() 核心实现

// 阻塞当前线程
public static void park() {
    U.park(false, 0L);
}

// 带阻塞对象的 park
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);  // 设置阻塞对象
    U.park(false, 0L);       // 调用 unsafe 的 park
    setBlocker(t, null);     // 清除阻塞对象
}

// 设置阻塞对象(通过 Unsafe 直接操作线程内存)
private static void setBlocker(Thread t, Object arg) {
    U.putObject(t, PARKBLOCKER, arg);
}

3. unpark() 实现

// 唤醒指定线程
public static void unpark(Thread thread) {
    if (thread != null) {
        U.unpark(thread);
    }
}

4. 带超时的 park 方法

// 阻塞指定纳秒数
public static void parkNanos(long nanos) {
    if (nanos > 0) {
        U.park(false, nanos);
    }
}

// 阻塞直到绝对时间(毫秒)
public static void parkUntil(long deadline) {
    U.park(true, deadline);
}

五、执行流程图

park
unpark
调用线程
许可可用?
消耗许可
立即返回
阻塞线程
其他线程
设置许可=1
线程阻塞中?
唤醒线程
保持许可可用

六、使用示例

1. 基础用法

Thread thread = new Thread(() -> {
    System.out.println("线程即将阻塞");
    LockSupport.park(); // 阻塞当前线程
    System.out.println("线程被唤醒");
});

thread.start();
Thread.sleep(1000);
LockSupport.unpark(thread); // 唤醒线程

2. 先唤醒后阻塞

Thread thread = new Thread(() -> {
    try {
        Thread.sleep(2000); // 确保先执行 unpark
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("执行 park");
    LockSupport.park();
    System.out.println("park 立即返回");
});

thread.start();
LockSupport.unpark(thread); // 先提供许可

3. 使用阻塞对象(诊断友好)

Object blocker = new Object();
Thread thread = new Thread(() -> {
    LockSupport.park(blocker); // 设置阻塞对象
});

thread.start();

// jstack 输出片段示例:
// "Thread-0" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x6d03 waiting on condition [0x00007f...]
//    java.lang.Thread.State: WAITING (parking)
//         at sun.misc.Unsafe.park(Native Method)
//         - parking to wait for  <0x000000076b6080c8> (a java.lang.Object)

七、与 wait/notify 的对比

特性LockSupportwait/notify
同步要求不需要同步块必须在同步块内使用
操作对象直接操作线程操作对象监视器
唤醒顺序精确唤醒指定线程随机唤醒一个等待线程
先唤醒后阻塞支持不支持
中断处理返回但不抛出异常抛出 InterruptedException
许可机制
超时控制支持纳秒级精度仅支持毫秒级

八、核心源码解析(Native 层)

1. park/unpark 的底层实现(Linux 版本)

// 在 HotSpot 的 os_linux.cpp 中
void Parker::park(bool isAbsolute, jlong time) {
    if (Atomic::xchg(0, &_counter) > 0) return;
    
    // 获取当前线程
    Thread* thread = Thread::current();
    
    // 检查中断状态
    if (thread->is_interrupted(false)) {
        return;
    }
    
    // 设置超时时间
    timespec absTime;
    if (time > 0) {
        to_abstime(&absTime, time, isAbsolute);
    }
    
    // 循环检查
    while (_counter < 1) {
        // 挂起线程(通过 futex 系统调用)
        int status = pthread_cond_timedwait(&_cond, &_mutex, &absTime);
        // 处理超时或中断
        if (status == ETIME || (status == EINTR && _counter < 1)) {
            break;
        }
    }
    
    // 重置计数器
    _counter = 0;
}

void Parker::unpark() {
    // 原子增加计数器
    if (Atomic::xchg(1, &_counter) > 0) return;
    
    // 唤醒等待的线程
    pthread_cond_signal(&_cond);
}

2. 关键实现点:

  1. 原子计数器:使用 _counter 实现许可机制
  2. 条件变量:通过 pthread_cond_timedwait 实现超时等待
  3. futex 系统调用:Linux 下的高效线程同步原语
  4. 中断处理:检查线程中断状态后直接返回

九、最佳实践与注意事项

  1. 避免永久阻塞

    // 推荐使用超时版本
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
    
  2. 结合中断使用

    while (!Thread.currentThread().isInterrupted()) {
        LockSupport.park(this);
        // 清除中断状态后继续
        Thread.interrupted();
    }
    
  3. 资源释放保证

    try {
        LockSupport.park();
    } finally {
        // 确保资源释放
    }
    
  4. 诊断优化

    // 使用阻塞对象提高诊断可读性
    LockSupport.park(lockObject);
    

十、性能特点

  1. 低开销:比 synchronized 轻量 20-30%
  2. 无竞争优化:在无竞争情况下几乎无开销
  3. 系统调用优化:Linux 下使用 futex 避免不必要的内核切换
  4. 缓存友好:许可状态使用单个变量,缓存效率高

总结

LockSupport 是 Java 并发编程的底层基石:

  • 基于 许可机制 实现线程阻塞/唤醒
  • 提供 无锁、精准 的线程控制能力
  • 支持 先唤醒后阻塞 的灵活模式
  • 通过 Unsafe 直接操作内存 保证高效性
  • 为 AQS 等高级同步组件提供基础支持

理解 LockSupport 的工作原理对于掌握 Java 并发模型至关重要,它解决了传统 wait/notify 机制的诸多限制,为构建高性能并发组件提供了坚实基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值