背景
并发编程学习中,看到了使用park()和unpark()方法,自己之前没见过,于是搜索了下,这两个方法的作用,先将搜索结果,整理如下:
并发编程中的park和unpark方法详解
unpark() 是 Java 中用于线程调度的一个重要方法,它与 park() 方法一起使用,提供了一种轻量级的线程阻塞和唤醒机制。unpark() 和 park() 是 Java 线程库中底层的同步原语,通常由更高层次的同步工具(如 Lock、Condition、Semaphore 等)内部使用,但也可以在自定义的同步逻辑中直接调用。
1. unpark() 和 park() 的基本概念
-
unpark(Thread thread):该方法用于唤醒一个被park()阻塞的线程。它会将指定的线程从阻塞状态中移除,使其恢复执行。如果该线程尚未调用park(),则unpark()会在后续的park()调用中立即生效。 -
park():该方法用于阻塞当前线程,直到发生以下情况之一:- 另一个线程调用了
unpark()来唤醒当前线程。 - 指定了超时时间,且超时时间已到。
- 线程被中断。
- 另一个线程调用了
park() 和 unpark() 是成对使用的,类似于 wait() 和 notify(),但它们提供了更灵活的控制方式,并且可以在任意线程之间进行阻塞和唤醒操作,而不仅仅是持有锁的线程。
2. unpark() 的工作原理
unpark() 的核心功能是为指定的线程设置一个“许可”(permit),当该线程调用 park() 时,它会检查是否有可用的许可。如果有许可,则线程不会被阻塞,而是继续执行;如果没有许可,则线程会被阻塞,直到另一个线程调用 unpark() 为其提供许可。
许可的工作流程:
- 初始状态:每个线程都有一个许可计数器,初始值为 0。
unpark()调用:当unpark()被调用时,它会为指定的线程增加一个许可(将许可计数器加 1)。如果该线程已经处于阻塞状态(即调用了park()),则它会立即被唤醒并继续执行。park()调用:当线程调用park()时,它会检查是否有可用的许可。如果有许可,则线程消耗一个许可(将许可计数器减 1)并继续执行;如果没有许可,则线程会被阻塞,直到另一个线程调用unpark()为其提供许可。
许可的特性:
- 许可是不可重入的:即使多次调用
unpark(),线程在调用park()时也只会消耗一个许可。也就是说,unpark()的调用次数并不会累积,每次park()只会消耗一个许可。 - 许可可以提前设置:
unpark()可以在park()之前调用,这样当线程后续调用park()时,它会立即获得许可并继续执行,而不会被阻塞。
3. unpark() 和 park() 的使用场景
unpark() 和 park() 主要用于实现自定义的同步机制,尤其是在需要精确控制线程阻塞和唤醒的情况下。它们比传统的 wait() 和 notify() 更加灵活,因为它们不需要依赖对象锁,并且可以在任意线程之间进行阻塞和唤醒操作。
典型使用场景:
-
线程池:Java 的线程池(如
ThreadPoolExecutor)内部使用了park()和unpark()来管理任务的提交和执行。当没有任务时,工作线程会调用park()进入阻塞状态,等待新的任务到来。当有新任务提交时,线程池会调用unpark()唤醒一个阻塞的工作线程来处理任务。 -
条件变量:
Lock和Condition类(如ReentrantLock和Condition)内部使用了park()和unpark()来实现线程的等待和通知。await()方法会调用park()来阻塞当前线程,而signal()方法会调用unpark()来唤醒一个等待的线程。 -
自定义同步器:如果你需要实现自己的同步器或锁机制,
park()和unpark()提供了底层的阻塞和唤醒功能,可以帮助你构建更复杂的同步逻辑。
4. unpark() 和 park() 的 API
unpark() 和 park() 是通过 java.util.concurrent.locks.LockSupport 类提供的静态方法。以下是这两个方法的签名:
public class LockSupport {
// 唤醒指定的线程
public static void unpark(Thread thread);
// 阻塞当前线程,直到被唤醒或超时
public static void park();
// 阻塞当前线程,直到被唤醒或超时到达
public static void parkNanos(long nanos);
// 阻塞当前线程,直到被唤醒或指定的时间到达
public static void parkUntil(long deadline);
}
-
unpark(Thread thread):唤醒指定的线程。如果该线程尚未调用park(),则许可会被保存,直到后续的park()调用时生效。 -
park():阻塞当前线程,直到被unpark()唤醒或线程被中断。 -
parkNanos(long nanos):阻塞当前线程,直到被unpark()唤醒或指定的纳秒时间过去。 -
parkUntil(long deadline):阻塞当前线程,直到被unpark()唤醒或指定的时间戳到达。
5. 示例代码
以下是一个简单的示例,展示了如何使用 unpark() 和 park() 来实现线程的阻塞和唤醒:
import java.util.concurrent.locks.LockSupport;
public class ParkUnparkExample {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("Thread T1 is about to park...");
LockSupport.park(); // 阻塞当前线程
System.out.println("Thread T1 has been unparked!");
});
t1.start();
// 让主线程稍微等待一下,确保 T1 已经调用了 park()
Thread.sleep(1000);
// 唤醒 T1 线程
System.out.println("Main thread is going to unpark T1...");
LockSupport.unpark(t1);
// 等待 T1 线程结束
t1.join();
}
}
输出结果:
Thread T1 is about to park...
Main thread is going to unpark T1...
Thread T1 has been unparked!
在这个示例中:
T1线程调用LockSupport.park()后进入阻塞状态,等待被唤醒。- 主线程调用
LockSupport.unpark(t1)唤醒了T1线程,T1线程随后继续执行并打印出消息。
6. unpark() 和 park() 的优点
- 灵活性:
unpark()和park()不依赖于对象锁,因此可以在任意线程之间进行阻塞和唤醒操作,提供了更大的灵活性。 - 性能:相比于
wait()和notify(),park()和unpark()的开销较小,适合高性能的并发编程。 - 精确控制:
park()和unpark()提供了更细粒度的控制,允许你在特定条件下阻塞和唤醒线程,而不必依赖锁的状态。
7. 注意事项
- 不要滥用:虽然
unpark()和park()提供了强大的线程控制能力,但在大多数情况下,你应该优先使用更高层次的同步工具(如Lock、Condition、Semaphore等),除非你确实需要自定义的同步逻辑。 - 许可的非重入性:
unpark()提供的许可是不可重入的,这意味着即使多次调用unpark(),线程在调用park()时也只会消耗一个许可。因此,你不应该依赖多次unpark()来实现多次park()的唤醒。 - 线程安全:
unpark()和park()是线程安全的,但你需要确保在多线程环境中正确使用它们,以避免死锁或其他并发问题。
8. 总结
unpark() 是 Java 中用于唤醒被 park() 阻塞的线程的方法。它与 park() 一起构成了一个轻量级的线程阻塞和唤醒机制,广泛应用于 Java 的并发库中,尤其是在线程池、锁和条件变量的实现中。通过 unpark() 和 park(),你可以实现更灵活和高效的线程调度,但需要注意它们的使用场景和潜在的风险。