文章目录

Java的LockSupport与线程阻塞唤醒

发布于 2026-06-02 00:10:48 · 浏览 20 次 · 评论 0 条

Java的LockSupport与线程阻塞唤醒

在多线程编程中,线程间的协调至关重要。传统的wait()/notify()机制存在严格的顺序要求和持有监视器锁的限制。LockSupport类提供了一种更底层、更灵活的线程阻塞与唤醒机制,是构建高级并发工具的基础。


一、LockSupport是什么?

LockSupportjava.util.concurrent.locks包下的一个工具类,核心功能是阻塞(park)唤醒(unpark) 指定线程。它直接使用许可(permit)机制,不依赖任何监视器锁。

二、核心方法速查

方法名 作用 参数说明
void park() 阻塞当前线程,直到许可可用或被中断。
void parkNanos(long nanos) 阻塞当前线程,最多等待指定时间(纳秒)。 nanos:最大等待纳秒数
void parkUntil(long deadline) 阻塞当前线程,直到指定的绝对时间戳(毫秒)。 deadline:绝对时间戳
void unpark(Thread thread) 唤醒(使能)指定线程。如果线程正在park中,则唤醒它;如果还未park,则为其后续的park()调用提供许可。 thread:目标线程

三、与传统wait/notify的关键区别

理解LockSupport的优越性,需要对比传统方式。

对比维度 Object.wait()/notify() LockSupport.park()/unpark()
前置条件 必须在synchronized块内调用,必须持有监视器锁。 无限制,可以在任何地方调用。
唤醒顺序 notify()是随机唤醒一个等待线程,无法指定。notifyAll()唤醒所有。 unpark(thread)精准唤醒指定线程。
阻塞/唤醒顺序 必须先wait(),后notify()。如果先notify(),信号会丢失。 顺序更灵活。可以先unpark()park(),线程会立即返回(消耗许可)。
状态传递 基于对象监视器(条件队列)。 基于线程的“许可”标志。

四、如何使用LockSupport:手把手指南

步骤1:理解“许可”机制

LockSupport的核心是每个线程关联一个布尔型的“许可”。

  • 默认状态:许可为false
  • 调用park():检查许可。
    • 若许可为true,则立即将其置为false并返回,线程不会阻塞。
    • 若许可为false,则阻塞线程。
  • 调用unpark(thread):将目标线程的许可置为true。如果该线程正在park()中,则被唤醒。

步骤2:实现基本阻塞与唤醒

import java.util.concurrent.locks.LockSupport;

public class BasicParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工作线程:准备进入阻塞...");
            // 步骤A: 阻塞当前线程
            LockSupport.park();
            System.out.println("工作线程:被唤醒,继续执行!");
        });
        worker.start();

        // 主线程休眠1秒,确保工作线程已启动并park
        Thread.sleep(1000);
        System.out.println("主线程:唤醒工作线程");
        // 步骤B: 唤醒指定的worker线程
        LockSupport.unpark(worker);
    }
}

执行结果

  1. 工作线程启动后打印第一条信息,然后调用park()阻塞。
  2. 主线程休眠后打印第二条信息,然后调用unpark(worker)
  3. 工作线程被唤醒,打印第三条信息。

步骤3:处理“先unpark后park”的场景

这是LockSupport的优势场景,避免了信号丢失。

import java.util.concurrent.locks.LockSupport;

public class UnparkFirstDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工作线程:即将park...");
            // 步骤B: 调用park。由于之前unpark已设置了许可,此处会立即返回。
            LockSupport.park();
            System.out.println("工作线程:park返回,继续执行!");
        });
        worker.start();

        // 主线程休眠很短时间,目的是让工作线程启动,但不一定已执行到park
        Thread.sleep(100);
        System.out.println("主线程:提前给工作线程发放许可(unpark)");
        // 步骤A: 先于park调用unpark。这会将worker的许可设为true。
        LockSupport.unpark(worker);
    }
}

执行结果
即使unpark发生在park之前,工作线程的park()调用也会立即返回,因为它检测到了有效的许可。

步骤4:实现可中断的阻塞

park()方法响应中断。线程被中断时会从park()中返回,但不会抛出InterruptedException。需要自己检查中断状态。

import java.util.concurrent.locks.LockSupport;

public class InterruptibleParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工作线程:开始park,等待中断...");
            LockSupport.park();
            // 被唤醒或中断后,检查中断标志
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("工作线程:是被中断唤醒的,处理中断逻辑。");
                // 根据需要决定是否清理中断标志
            } else {
                System.out.println("工作线程:是被正常unpark唤醒的。");
            }
        });
        worker.start();

        Thread.sleep(1000);
        System.out.println("主线程:中断工作线程");
        // 中断worker线程,这会导致其park()返回
        worker.interrupt();
    }
}

步骤5:实现带超时的阻塞

使用parkNanosparkUntil实现等待超时。

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.TimeUnit;

public class ParkWithTimeoutDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            long start = System.currentTimeMillis();
            System.out.println("工作线程:开始park,最多等待2秒...");
            // park最多2秒(转换为纳秒)
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
            long elapsed = System.currentTimeMillis() - start;
            System.out.println("工作线程:park返回,实际等待约" + elapsed + "ms。");
        });
        worker.start();

        // 主线程不调用unpark,让工作线程等待超时
        Thread.sleep(3000);
    }
}

执行结果:工作线程等待约2秒后自动从parkNanos返回。


五、高级应用场景与模式

场景1:构建简单的锁(自定义互斥锁)

LockSupport是实现锁(如ReentrantLock)的底层关键。下面是一个极简示例:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

public class SimpleLock {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private Thread owner = null; // 持有锁的线程

    public void lock() {
        // 自旋尝试获取锁
        while (!locked.compareAndSet(false, true)) {
            // 获取失败,阻塞自己,等待被唤醒后重试
            LockSupport.park();
        }
        owner = Thread.currentThread();
        System.out.println(owner.getName() + " 获得了锁。");
    }

    public void unlock() {
        if (Thread.currentThread() != owner) {
            throw new IllegalMonitorStateException();
        }
        owner = null;
        locked.set(false);
        // 唤醒一个可能正在等待获取锁的线程
        // 实际上,这里唤醒的是哪个线程是不确定的,因为park在while循环中
        // 更复杂的实现需要维护一个等待队列(如AQS)
        Thread waiter = ...; // 需要一个机制找到等待者,这里仅示意
        if (waiter != null) {
            LockSupport.unpark(waiter);
        }
        System.out.println(Thread.currentThread().getName() + " 释放了锁。");
    }
}

注意:这是一个高度简化的模型,真实锁实现(如AQS)远比这复杂,需要精确管理等待队列和公平性。

场景2:实现生产者-消费者中的唤醒

利用LockSupport的精确唤醒特性,可以设计更高效的生产消费模式。

import java.util.Queue;
import java.util.LinkedList;
import java.util.concurrent.locks.LockSupport;

public class ParkBasedProducerConsumer {
    private final Queue<String> queue = new LinkedList<>();
    private Thread consumerThread;

    public void startConsumer() {
        consumerThread = new Thread(() -> {
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            // 暂时释放锁,并在队列上等待(这里用wait更合适,但为演示park)
                            // 实际上,在监视器锁内部使用park是危险的,可能导致锁无法释放
                            // 此处仅为逻辑示意,建议在锁外部使用park
                            queue.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    String item = queue.poll();
                    System.out.println("消费: " + item);
                }
            }
        });
        consumerThread.start();
    }

    public void produce(String item) {
        synchronized (queue) {
            queue.add(item);
            System.out.println("生产: " + item);
            // 唤醒消费者线程。由于我们在锁内操作,这里用notify更合适。
            // 如果用park/unpark,需要在锁外进行,设计更复杂。
            LockSupport.unpark(consumerThread);
        }
    }
}

重要提示:在典型的监视器锁(synchronized)内部使用LockSupport需要非常小心,容易造成死锁或程序行为异常。通常,LockSupport与基于它构建的高级同步器(如ConditionCountDownLatch)结合使用。


六、使用注意事项

  1. 许可的单一性:许可是每个线程独有的布尔值,不是累加的。连续调用两次unpark和一次unpark的效果相同(许可仍为true)。一次park消耗一次许可。
  2. 中断处理park()因中断返回后,线程的中断标志保持设置状态。需要根据业务逻辑决定是抛出异常、清理退出还是继续执行。
  3. 避免在synchronized块内长期park:如果线程在持有监视器锁(synchronized)时park,其他线程将无法获取该锁去unpark它,可能导致死锁。LockSupport.park()通常用于Lock接口的实现内部或独立的阻塞场景。
  4. 调试:被park阻塞的线程,在调试工具或jstack中会显示为WAITING (parking)状态。
  5. park可能虚假唤醒:在没有明确unpark、中断或超时的情况下,park也可能返回。因此,必须在循环条件中检查是否真的满足继续执行的条件(while(!condition) park();)。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文