[java - LockSupport sleep wait ]

没问题:LockSupportsleepwait在多线程编程里作用不同,我一个一个给你讲,然后再对比着说,这样能讲得更透彻。

1. LockSupport

LockSupport是Java并发包java.util.concurrent.locks里的一个类,主要用于线程的阻塞和唤醒。它提供了park()unpark()方法。

  • park():这个方法会阻塞当前线程,除非有以下几种情况发生才会被唤醒:
    • 另一个线程调用了当前线程的unpark()方法。
    • 线程被中断。
    • 发生了虚假唤醒(虽然概率极低,但在编写代码时要考虑这种情况)。
  • unpark(Thread thread):唤醒指定的线程。如果线程thread正在被park()阻塞,那么它会立即被唤醒继续执行;如果该线程还没调用park(),那么下一次调用park()时会直接跳过阻塞。

2. sleep

sleepThread类的一个静态方法,作用是让当前线程暂停执行一段指定的时间。

  • Thread.sleep(long millis):线程会暂停执行指定的毫秒数。在这段时间内,线程不会占用CPU资源,会进入睡眠状态。
  • Thread.sleep(long millis, int nanos):这个方法可以更精确地指定暂停时间,精确到纳秒。不过由于操作系统的调度等原因,实际的暂停时间可能会略有偏差。
    需要注意的是,sleep方法不会释放锁。如果线程在持有锁的情况下调用sleep,其他线程仍然无法获取该锁,直到线程从睡眠中醒来并继续执行,释放锁后其他线程才有机会获取。

3. wait

waitObject类的一个方法,用于让线程在某个条件不满足时等待,直到被其他线程唤醒。

  • wait():会使当前线程进入等待状态,同时释放对象的锁。这一点和sleep有很大区别,释放锁后其他线程就可以获取该锁并执行相关操作。
  • wait(long timeout):线程会等待指定的时间,或者直到被其他线程唤醒。如果在指定时间内没有被唤醒,线程会自动醒来继续执行。
  • wait(long timeout, int nanos):同样是更精确地指定等待时间,精确到纳秒。
    调用wait方法必须在synchronized代码块中,否则会抛出IllegalMonitorStateException异常。

4. 对比总结

  • 锁的处理
    • LockSupport不涉及锁的释放和获取,它主要是控制线程的阻塞和唤醒,与锁机制相对独立。
    • sleep不会释放锁,持有锁时调用sleep可能会导致死锁,因为其他线程无法获取该锁。
    • wait会释放对象锁,使得其他线程有机会获取锁执行操作,这是它和sleep的重要区别。
  • 唤醒方式
    • LockSupport通过unpark()方法唤醒指定线程,灵活性较高,而且一个线程可以多次调用`

我的理解: LockSupport 最好
sleep不释放锁,wait 之前 收到 notify 然后wait 就会一直 wait ,只有 LockSupport 支持 之前收到unpark信号 ,然后 park 就直接跳过,信号不会丢失

LockSupport 是 Java 并发包里的一个底层工具类,核心功能是“阻塞/唤醒线程”,但它的设计和 Thread.sleep()Object.wait() 有本质区别。咱们从 使用场景、底层原理、核心差异 三个维度彻底讲透:

一、先明确三者的核心功能

  • Thread.sleep(long):让当前线程暂停执行指定时间,时间到后自动唤醒。
  • Object.wait():让当前线程阻塞,等待其他线程调用同一个对象的 notify()/notifyAll() 唤醒。
  • LockSupport.park():让当前线程阻塞,等待其他线程调用 LockSupport.unpark(Thread) 唤醒(指定线程唤醒)。

二、核心差异对比(用表格一目了然)

对比项 Thread.sleep(long) Object.wait() LockSupport.park()
是否需要锁 不需要(不涉及锁) 必须在 synchronized 块中调用(持有对象锁) 不需要(无锁要求)
唤醒方式 时间到自动唤醒 其他线程调用 notify()/notifyAll() 其他线程调用 unpark(目标线程)
唤醒的针对性 无(只能自己醒) 随机唤醒一个或全部等待线程(无法指定) 精准唤醒指定线程(参数是目标线程)
阻塞时是否释放锁 不释放(本身不持有锁) 释放持有的对象锁 不释放(本身不持有锁)
“提前唤醒”处理 无法提前唤醒(只能等时间到) 可能被虚假唤醒(需用 while 循环检查) 支持“先唤醒后阻塞”(不会丢信号)

三、逐个拆解:为什么这些差异很重要?

1. Thread.sleep():最简单的“定时休眠”

  • 特点:不涉及锁,调用后线程进入 TIMED_WAITING 状态,时间到自动恢复。
  • 典型场景:简单的延迟执行(比如每隔1秒打印一次日志)。
  • 注意
    • 不会释放任何锁(因为它本来就不要求持有锁)。
    • 无法被“提前唤醒”(除非被中断,interrupt() 会抛出 InterruptedException)。

2. Object.wait():基于“对象锁”的等待唤醒

  • 特点:必须在 synchronized 块中调用(持有锁),调用后释放锁,线程进入 WAITING 状态,需被 notify() 唤醒。
  • 典型场景:多线程协作(比如生产者-消费者模型,消费者等生产者唤醒)。
  • 核心问题
    • 必须依赖锁:如果忘了加 synchronized,直接抛 IllegalMonitorStateException
    • 唤醒不精准notify() 随机唤醒一个等待线程,notifyAll() 唤醒所有,无法指定唤醒某个线程,可能导致“惊群效应”(大量线程被唤醒后竞争锁,大部分又阻塞)。
    • 可能虚假唤醒:即使没有被 notify(),也可能被JVM随机唤醒,所以必须用 while 循环检查条件(比如 while (!条件) { wait(); })。

3. LockSupport.park():更灵活的“无锁阻塞唤醒”

LockSupportUnsafe 类的封装,底层直接操作线程的状态,是 ReentrantLockCountDownLatch 等并发工具的“底层发动机”。

  • 核心优势
    无锁依赖:不需要在 synchronized 或锁块中调用,直接阻塞当前线程,更灵活。
    精准唤醒unpark(thread) 可以指定唤醒某个线程,避免“惊群效应”。
    信号不丢失:支持“先唤醒,后阻塞”——如果先调用 unpark(thread),再调用 thread.park()park() 会直接返回(不会阻塞)。而 wait() 如果先调用 notify()wait(),会永远阻塞(信号丢失)。
    可中断park() 阻塞时,其他线程调用 thread.interrupt() 会让它立即返回(不会抛异常,但可以通过 Thread.interrupted() 检查中断状态)。

  • 典型场景:实现高级并发工具(如 ReentrantLockCondition 等待/唤醒、ThreadLocal 的回收机制等),或需要精准控制线程的场景。

四、用代码示例看差异

示例1:wait() 的“信号丢失”问题

Object lock = new Object();
Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try {
            // 假设先执行了notify(),再执行wait()
            System.out.println("t1等待");
            lock.wait(); // 会永远阻塞,因为信号已经丢了
            System.out.println("t1被唤醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
t1.start();

// 主线程先调用notify()
synchronized (lock) {
    lock.notify();
    System.out.println("主线程唤醒");
}

结果:t1 会永远阻塞,因为 notify()wait() 之前执行,信号丢失。

示例2:LockSupport 解决“信号丢失”

Thread t1 = new Thread(() -> {
    System.out.println("t1等待");
    LockSupport.park(); // 阻塞(如果之前有unpark,这里直接通过)
    System.out.println("t1被唤醒");
});
t1.start();

// 主线程先调用unpark()
LockSupport.unpark(t1); // 提前发送唤醒信号
System.out.println("主线程唤醒");

结果:t1park() 会直接返回,打印“t1被唤醒”——信号不会丢失。

示例3:LockSupport 精准唤醒指定线程

Thread t1 = new Thread(() -> {
    System.out.println("t1阻塞");
    LockSupport.park();
    System.out.println("t1被唤醒");
}, "t1");

Thread t2 = new Thread(() -> {
    System.out.println("t2阻塞");
    LockSupport.park();
    System.out.println("t2被唤醒");
}, "t2");

t1.start();
t2.start();

// 精准唤醒t1(t2继续阻塞)
LockSupport.unpark(t1);

结果:只有 t1 被唤醒,t2 保持阻塞——这是 wait() 做不到的(notify() 无法指定线程)。

五、总结:什么时候用哪种?

  • 简单定时延迟:用 Thread.sleep()(最简单)。
  • 基于对象锁的多线程协作(且能接受随机唤醒):用 Object.wait() + notify()(依赖 synchronized)。
  • 需要灵活控制(无锁、精准唤醒、防信号丢失):用 LockSupport.park() + unpark()(底层工具,适合实现复杂并发逻辑)。

一句话:LockSupport 是更底层、更灵活的线程阻塞唤醒工具,摆脱了对 synchronized 锁的依赖,解决了 wait() 的信号丢失和唤醒不精准问题,是Java并发工具的基础。

posted @ 2025-10-28 09:50  十三山入秋  阅读(1)  评论(2)    收藏  举报