控制线程执行顺序的三种方法
在多线程编程中,有时我们需要控制多个线程的执行顺序。例如,线程 t1 必须先于 t2 执行,t2 必须先于 t3 执行。以下是三种常见的方法来控制线程的顺序:
1. 使用 Thread.join()
join() 方法是 Java 中用于控制线程执行顺序的一个简单而直接的方式。通过调用一个线程的 join() 方法,当前线程会等待被调用的线程执行完毕后再继续执行。
如何控制顺序:
假设有线程 t1、t2 和 t3,要确保 t1 在 t2 之前执行,t2 在 t3 之前执行,可以这样做:
Thread t1 = new Thread(() -> {
// 执行 t1 的任务
});
Thread t2 = new Thread(() -> {
// 执行 t2 的任务
});
Thread t3 = new Thread(() -> {
// 执行 t3 的任务
});
t1.start();
t1.join(); // 等待 t1 执行完再执行 t2
t2.start();
t2.join(); // 等待 t2 执行完再执行 t3
t3.start();
t3.join(); // 等待 t3 执行完
特点:
-
join()方法会阻塞调用线程,直到被调用线程执行完毕。 -
简单易用,但无法实现更复杂的线程同步需求。
2. 使用 CountDownLatch
CountDownLatch 是 Java 提供的一个线程同步工具,它允许一个线程等待多个线程执行完毕后再继续执行。CountDownLatch 初始化时给定一个计数器值,计数器每次调用 countDown() 方法减少,直到计数器为零,等待的线程才会继续执行。
如何控制顺序:
使用 CountDownLatch 来控制线程顺序,我们可以让 t1 执行完成后减少计数器,并让 t2 等待计数器为零后再执行。
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
// 执行 t1 的任务
latch1.countDown(); // t1 执行完毕,通知 t2
});
Thread t2 = new Thread(() -> {
try {
latch1.await(); // 等待 t1 执行完毕
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 执行 t2 的任务
latch2.countDown(); // t2 执行完毕,通知 t3
});
Thread t3 = new Thread(() -> {
try {
latch2.await(); // 等待 t2 执行完毕
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 执行 t3 的任务
});
t1.start();
t2.start();
t3.start();
特点:
-
CountDownLatch可以控制多个线程的顺序,适用于多个线程的同步。 -
在计数器达到零后,所有等待线程将同时被释放。
-
一次性使用,计数器不能重置。
3. 使用 LockSupport
LockSupport 是一个低层次的线程同步工具,提供了 park() 和 unpark() 方法来控制线程的阻塞和唤醒。park() 会使线程阻塞,而 unpark() 可以唤醒一个被阻塞的线程。
如何控制顺序:
通过 LockSupport.park() 和 LockSupport.unpark(),我们可以精确控制线程的执行顺序。例如,确保 t1 在 t2 之前执行,t2 在 t3 之前执行:
Thread t1 = new Thread(() -> {
// 执行 t1 的任务
LockSupport.unpark(t2); // 唤醒 t2
});
Thread t2 = new Thread(() -> {
LockSupport.park(); // 等待 t1 执行完毕
// 执行 t2 的任务
LockSupport.unpark(t3); // 唤醒 t3
});
Thread t3 = new Thread(() -> {
LockSupport.park(); // 等待 t2 执行完毕
// 执行 t3 的任务
});
t1.start();
t2.start();
t3.start();
特点:
-
LockSupport提供了更细粒度的控制,适用于复杂的线程同步需求。 -
需要显式调用
unpark()来唤醒阻塞的线程。 -
更为灵活,但使用时需要小心避免死锁或线程未被唤醒的情况。
对比总结:
| 方法 | 特点 | 优点 | 缺点 |
|---|---|---|---|
join() | 当前线程等待另一个线程执行完再继续 | 简单易用,适合控制线程执行顺序 | 无法控制多个线程同步,较为局限 |
CountDownLatch | 通过计数器来控制线程的同步,线程等待计数器为零再继续 | 适合多个线程的同步,能够控制多个线程顺序 | 计数器只能使用一次,不能重置 |
LockSupport | 提供 park() 和 unpark() 方法来控制线程阻塞和唤醒 | 灵活性高,适合复杂的线程控制需求 | 使用复杂,容易造成死锁,需要显式调用唤醒 |
应用场景:
-
join():适用于简单的线程执行顺序控制,线程间的依赖关系较少时使用。 -
CountDownLatch:适用于等待多个线程完成后再继续执行的场景,或者当有多个线程需要同步时。 -
LockSupport:适用于复杂的线程控制,尤其是需要精确控制线程暂停和唤醒时的场景。

8537

被折叠的 条评论
为什么被折叠?



