回答
在 Java 中,线程的执行顺序默认由 JVM 和操作系统调度器决定,无法直接保证。但通过特定的同步机制和工具,可以控制多个线程的执行顺序。以下是几种常见方法及其实现原理:
1. 使用 join()
方法
- 原理:
Thread.join()
使当前线程等待目标线程完成后继续执行。 - 适用场景:简单线性顺序。
- 示例:
public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> System.out.println("Thread 1")); Thread t2 = new Thread(() -> System.out.println("Thread 2")); Thread t3 = new Thread(() -> System.out.println("Thread 3")); t1.start(); t1.join(); // 等待 t1 完成 t2.start(); t2.join(); // 等待 t2 完成 t3.start(); t3.join(); // 等待 t3 完成 System.out.println("All threads finished"); } }
- 输出:
Thread 1 Thread 2 Thread 3 All threads finished
- 优点:简单直观。
- 缺点:只能控制线性依赖,不够灵活。
2. 使用 CountDownLatch
- 原理:通过计数器协调线程,等待所有前置线程完成。
- 适用场景:一组线程完成后另一组开始。
- 示例:
import java.util.concurrent.*; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(1); Thread t1 = new Thread(() -> { System.out.println("Thread 1"); latch1.countDown(); }); Thread t2 = new Thread(() -> { try { latch1.await(); System.out.println("Thread 2"); latch2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { latch2.await(); System.out.println("Thread 3"); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t3.start(); } }
- 输出:
Thread 1 Thread 2 Thread 3
- 优点:支持多线程等待一个条件。
- 缺点:不可重用,需提前规划计数。
3. 使用 CyclicBarrier
- 原理:多个线程互相等待到达屏障点后继续。
- 适用场景:线程按阶段同步执行。
- 示例:
import java.util.concurrent.*; public class CyclicBarrierExample { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("Barrier passed")); Runnable task = () -> { try { System.out.println(Thread.currentThread().getName() + " ready"); barrier.await(); System.out.println(Thread.currentThread().getName() + " running"); } catch (Exception e) { e.printStackTrace(); } }; new Thread(task, "T1").start(); new Thread(task, "T2").start(); new Thread(task, "T3").start(); } }
- 输出(顺序可能不同):
T1 ready T2 ready T3 ready Barrier passed T1 running T2 running T3 running
- 优点:可重用,支持阶段性同步。
- 缺点:线程数需固定。
4. 使用 synchronized
和 wait()/notify()
- 原理:通过锁和条件变量手动控制顺序。
- 适用场景:复杂顺序需求。
- 示例:
public class WaitNotifyExample { private static final Object lock = new Object(); private static volatile int step = 1; public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock) { System.out.println("Thread 1"); step = 2; lock.notifyAll(); } }); Thread t2 = new Thread(() -> { synchronized (lock) { while (step != 2) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread 2"); step = 3; lock.notifyAll(); } }); Thread t3 = new Thread(() -> { synchronized (lock) { while (step != 3) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread 3"); } }); t3.start(); t2.start(); t1.start(); } }
- 输出:
Thread 1 Thread 2 Thread 3
- 优点:灵活性高。
- 缺点:代码复杂,易出错。
5. 使用 Semaphore
- 原理:通过许可控制线程执行顺序。
- 适用场景:顺序执行资源访问。
- 示例:
import java.util.concurrent.*; public class SemaphoreExample { public static void main(String[] args) { Semaphore s1 = new Semaphore(1); Semaphore s2 = new Semaphore(0); Semaphore s3 = new Semaphore(0); new Thread(() -> { try { s1.acquire(); System.out.println("Thread 1"); s2.release(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { s2.acquire(); System.out.println("Thread 2"); s3.release(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { s3.acquire(); System.out.println("Thread 3"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
- 输出:
Thread 1 Thread 2 Thread 3
- 优点:直观控制顺序。
- 缺点:需要多个信号量。
问题分析与知识点联系
“控制线程执行顺序”是并发编程中的常见需求,与问题列表中的多个知识点相关:
-
Java 中的线程同步
synchronized
、Semaphore
等实现顺序控制。 -
Java 中的线程通信
wait()/notify()
、CountDownLatch
、CyclicBarrier
用于线程间协调。 -
Java 使用过哪些并发工具类
CountDownLatch
、CyclicBarrier
、Semaphore
是常用工具。 -
线程的生命周期
join()
和await()
影响线程状态(如WAITING
)。 -
Java 中的死锁
不当使用同步工具可能导致死锁,需谨慎设计。
总结来说,Java 提供了多种方法控制线程执行顺序,从简单的 join()
到复杂的 Semaphore
组合,适用于不同场景。选择方法需根据线程数量、顺序复杂度和重用性要求,是并发编程中协调线程行为的核心技能。