解锁多线程顺序执行:四种经典写法详解
2023-11-24 19:09:08
前言
多线程顺序执行是并发编程中常见问题,也是面试中经常遇到的考题。掌握几种经典写法,不仅能够提高编程能力,更能让你在面试中脱颖而出。本文将介绍四种经典写法:join写法(两种写法)、线程池写法、CountDownLatch、CyclicBarrier、Phaser,并进行详细对比,帮助你深入理解和灵活运用这些方法。
join写法
join写法是最简单直接的多线程顺序执行方法,也是最容易理解的。基本思路是让主线程等待子线程执行完毕,然后再继续执行。有两种join写法:
1. 直接join写法
public class JoinDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("子线程执行");
});
thread.start();
thread.join(); // 等待子线程执行完毕
System.out.println("主线程执行");
}
}
2. join超时写法
public class JoinWithTimeoutDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("子线程执行");
try {
Thread.sleep(1000); // 模拟子线程执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
thread.join(500); // 等待子线程执行完毕,最多等待500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行");
}
}
join写法简单易懂,但在某些情况下可能会存在性能问题。例如,如果子线程需要执行很长时间,那么主线程就会一直等待,导致程序整体效率低下。因此,在实际应用中,更推荐使用线程池或其他更高级的并发编程工具。
线程池写法
线程池是并发编程中常用的工具,可以有效管理线程的生命周期,提高程序并发性能。使用线程池实现多线程顺序执行也非常简单:
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(() -> {
System.out.println("子线程执行");
return "子线程执行结果";
});
String result = future.get(); // 等待子线程执行完毕,并获取结果
System.out.println("主线程执行");
}
}
线程池写法与join写法相比,具有更好的性能和可伸缩性。它可以同时执行多个任务,提高程序的并发效率。同时,线程池还提供了丰富的管理功能,可以方便地控制线程的数量、生命周期和资源分配。
CountDownLatch写法
CountDownLatch是一种同步工具类,可以实现线程之间的等待和唤醒。使用CountDownLatch实现多线程顺序执行也很简单:
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
Thread thread = new Thread(() -> {
System.out.println("子线程执行");
countDownLatch.countDown(); // 减少计数器
});
thread.start();
try {
countDownLatch.await(); // 等待计数器变为0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行");
}
}
CountDownLatch写法与join写法类似,但它具有更丰富的功能。CountDownLatch可以实现多个线程同时等待一个或多个事件的发生,然后才继续执行。这使得CountDownLatch在某些场景下更加灵活和实用。
CyclicBarrier写法
CyclicBarrier是一种同步工具类,可以实现线程之间的循环等待和唤醒。使用CyclicBarrier实现多线程顺序执行也非常简单:
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread thread1 = new Thread(() -> {
System.out.println("子线程1执行");
try {
cyclicBarrier.await(); // 等待所有线程都到达屏障
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("子线程1继续执行");
});
Thread thread2 = new Thread(() -> {
System.out.println("子线程2执行");
try {
cyclicBarrier.await(); // 等待所有线程都到达屏障
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("子线程2继续执行");
});
thread1.start();
thread2.start();
}
}
CyclicBarrier写法与CountDownLatch写法类似,但它允许线程在到达屏障后继续执行。这使得CyclicBarrier在某些场景下更加灵活和实用。
Phaser写法
Phaser是一种同步工具类,可以实现线程之间的分阶段等待和唤醒。使用Phaser实现多线程顺序执行也非常简单:
public class PhaserDemo {
public static void main(String[] args) {
Phaser phaser = new Phaser(2);
Thread thread1 = new Thread(() -> {
System.out.println("子线程1执行");
phaser.arriveAndAwaitAdvance(); // 到达第一个阶段并等待所有线程都到达该阶段
System.out.println("子线程1继续执行");
});
Thread thread2 = new Thread(() -> {
System.out.println("子线程2执行");
phaser.arriveAndAwaitAdvance(); // 到达第一个阶段并等待所有线程都到达该阶段
System.out.println("子线程2继续执行");
});
thread1.start();
thread2.start();
}
}
Phaser写法与CyclicBarrier写法类似,但它允许线程在到达阶段后继续执行。这使得Phaser在某些场景下更加灵活和实用。
写法对比
写法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
join写法 | 简单易懂 | 性能可能较差 | 子线程执行时间较短的场景 |
线程池写法 | 性能好,可伸缩性强 | 管理线程复杂度较高 | 子线程执行时间较长的场景 |
CountDownLatch写法 | 功能丰富,灵活实用 | 使用场景有限 | 多个线程同时等待一个或多个事件的发生 |
CyclicBarrier写法 | 灵活实用,允许线程到达屏障后继续执行 | 使用场景有限 | 多个线程分阶段执行任务 |
Phaser写法 | 灵活实用,允许线程到达阶段后继续执行 | 使用场景有限 | 多个线程分阶段执行任务 |
总结
多线程顺序执行是并发编程中常见问题,掌握四种经典写法,能够帮助你轻松应对面试挑战。本文介绍了join写法、线程池写法、CountDownLatch、CyclicBarrier、Phaser,并进行了详细对比,希望对你有所帮助。在实际应用中,你可以根据具体场景选择最合适的写法,以提高程序的性能和可靠性。