多线程顺序执行,只知道两种?

1. 前言

随着计算机技术的不断发展,多线程已经成为了很多程序员日常开发中必不可少的一部分,尤其是在高并发的场景下,多线程可以充分利用CPU资源,提高程序的执行效率。然而,多线程带来的并发执行也可能导致程序结果的不确定性,因此如何实现多线程的顺序执行也成为了重要的问题。

本文将介绍两种多线程顺序执行的方式,并对它们的使用场景、优缺点进行说明,并给出相应代码示例。

2. 方式一:使用join()方法

2.1 使用场景

主线程可以调用子线程的join()方法,使得主线程等待子线程执行完毕后再继续执行主线程。这种方式适合于在主线程中创建多个子线程,且需要保证它们的执行顺序。

2.2 代码示例

class MyThread extends Thread {

private String name;

public MyThread(String name) {

this.name = name;

}

public void run() {

for (int i = 0; i < 4; i++) {

System.out.println(name + " run " + i);

}

}

}

public class Test {

public static void main(String[] args) {

MyThread t1 = new MyThread("Thread1");

MyThread t2 = new MyThread("Thread2");

MyThread t3 = new MyThread("Thread3");

t1.start();

try {

t1.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

t2.start();

try {

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

t3.start();

try {

t3.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

上述代码中,在主线程中先创建了三个子线程t1、t2、t3,然后依次调用每个子线程的start()方法,再使用join()方法让主线程等待每个子线程执行完毕后再开始下一个子线程。在MyThread类的run()方法中,每个子线程都循环输出一段文本,以便我们观察执行顺序。

在运行上述代码后,可以看到如下输出:

Thread1 run 0

Thread1 run 1

Thread1 run 2

Thread1 run 3

Thread2 run 0

Thread2 run 1

Thread2 run 2

Thread2 run 3

Thread3 run 0

Thread3 run 1

Thread3 run 2

Thread3 run 3

可以看到,三个子线程的执行顺序是按照t1、t2、t3的顺序依次执行。

2.3 优缺点分析

使用join()方法实现多线程顺序执行的优点是比较简单易用,并且不会有锁或者同步的问题,保证了执行的可靠性。缺点是每个子线程必须等待前一个子线程执行完毕才能开始执行,因此效率上不如其他方式。

3. 方式二:使用CountDownLatch类

3.1 使用场景

CountDownLatch类是Java并发包中提供的一种同步工具,可以使得一个线程等待其他线程完成它们的工作后再执行。适合于需要在主线程中等待多个子线程全部执行结束后再执行主线程的情况。

3.2 代码示例

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class Test {

public static void main(String[] args) {

CountDownLatch latch = new CountDownLatch(3);

ExecutorService executor = Executors.newFixedThreadPool(3);

for (int i = 0; i < 3; i++) {

executor.execute(new MyTask(latch));

}

executor.shutdown();

try {

latch.await();

System.out.println("All tasks finished!");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

class MyTask implements Runnable {

private CountDownLatch latch;

public MyTask(CountDownLatch latch) {

this.latch = latch;

}

public void run() {

System.out.println(Thread.currentThread().getName() + " start to run!");

try {

Thread.sleep((long) (Math.random() * 1000));

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + " finished!");

latch.countDown();

}

}

在上述代码中,首先创建了一个CountDownLatch对象,初始值为3。然后创建了一个固定大小为3的线程池,依次提交三个任务。MyTask类实现了Runnable接口,每个任务执行时会随机休眠一段时间然后输出一行文本,以便我们观察执行顺序。在每个任务最后,会调用CountDownLatch的countDown()方法,使得CountDownLatch的计数器减1.

主线程中调用latch.await()方法,会等待CountDownLatch中的计数器变为0后再继续执行。当三个任务全部执行完毕后,CountDownLatch的计数器为0,主线程中会输出一行文本。

运行上述代码后,可以看到如下输出:

pool-1-thread-2 start to run!

pool-1-thread-1 start to run!

pool-1-thread-3 start to run!

pool-1-thread-2 finished!

pool-1-thread-1 finished!

pool-1-thread-3 finished!

All tasks finished!

可以看到,三个任务的执行顺序是随机的,但是主线程始终会等待三个任务执行完毕后再输出All tasks finished!

3.3 优缺点分析

使用CountDownLatch类实现多线程顺序执行的优点是可以自由控制子线程的执行顺序,而且执行效率也比较高。缺点是需要手动计数器来控制线程执行,复杂度略高,同时也可能出现CountDownLatch计数器值不为0的情况,导致主线程一直处于阻塞状态。

4. 总结

本文介绍了两种多线程顺序执行的方式,一种是使用join()方法,另一种是使用CountDownLatch类。这两种方式的使用场景、优缺点都有所区别,具体应根据开发需求来选择。在实际开发中,多线程顺序执行并非全部情况需求,对于一些需要提高效率、并且没有时间和先后顺序要求的任务,可以采用多线程并发执行的方式,这需要程序员在使用多线程时根据实际情况来决定。

后端开发标签