[java - LockSupport sleep wait ]
没问题:LockSupport、sleep和wait在多线程编程里作用不同,我一个一个给你讲,然后再对比着说,这样能讲得更透彻。
1. LockSupport
LockSupport是Java并发包java.util.concurrent.locks里的一个类,主要用于线程的阻塞和唤醒。它提供了park()和unpark()方法。
park():这个方法会阻塞当前线程,除非有以下几种情况发生才会被唤醒:- 另一个线程调用了当前线程的
unpark()方法。 - 线程被中断。
- 发生了虚假唤醒(虽然概率极低,但在编写代码时要考虑这种情况)。
- 另一个线程调用了当前线程的
unpark(Thread thread):唤醒指定的线程。如果线程thread正在被park()阻塞,那么它会立即被唤醒继续执行;如果该线程还没调用park(),那么下一次调用park()时会直接跳过阻塞。
2. sleep
sleep是Thread类的一个静态方法,作用是让当前线程暂停执行一段指定的时间。
Thread.sleep(long millis):线程会暂停执行指定的毫秒数。在这段时间内,线程不会占用CPU资源,会进入睡眠状态。Thread.sleep(long millis, int nanos):这个方法可以更精确地指定暂停时间,精确到纳秒。不过由于操作系统的调度等原因,实际的暂停时间可能会略有偏差。
需要注意的是,sleep方法不会释放锁。如果线程在持有锁的情况下调用sleep,其他线程仍然无法获取该锁,直到线程从睡眠中醒来并继续执行,释放锁后其他线程才有机会获取。
3. wait
wait是Object类的一个方法,用于让线程在某个条件不满足时等待,直到被其他线程唤醒。
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():更灵活的“无锁阻塞唤醒”
LockSupport 是 Unsafe 类的封装,底层直接操作线程的状态,是 ReentrantLock、CountDownLatch 等并发工具的“底层发动机”。
-
核心优势:
① 无锁依赖:不需要在synchronized或锁块中调用,直接阻塞当前线程,更灵活。
② 精准唤醒:unpark(thread)可以指定唤醒某个线程,避免“惊群效应”。
③ 信号不丢失:支持“先唤醒,后阻塞”——如果先调用unpark(thread),再调用thread.park(),park()会直接返回(不会阻塞)。而wait()如果先调用notify()再wait(),会永远阻塞(信号丢失)。
④ 可中断:park()阻塞时,其他线程调用thread.interrupt()会让它立即返回(不会抛异常,但可以通过Thread.interrupted()检查中断状态)。 -
典型场景:实现高级并发工具(如
ReentrantLock的Condition等待/唤醒、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("主线程唤醒");
结果:t1 的 park() 会直接返回,打印“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并发工具的基础。

浙公网安备 33010602011771号