返回

解锁多线程顺序执行:四种经典写法详解

后端

前言

多线程顺序执行是并发编程中常见问题,也是面试中经常遇到的考题。掌握几种经典写法,不仅能够提高编程能力,更能让你在面试中脱颖而出。本文将介绍四种经典写法: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,并进行了详细对比,希望对你有所帮助。在实际应用中,你可以根据具体场景选择最合适的写法,以提高程序的性能和可靠性。