保证多线程顺序执行,四种方案,你知道几种?

144 阅读4分钟

final Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程1");

}

});

final Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程2");

}

});

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程3");

}

});

t1.start();

t2.start();

t3.start();

}

}

运行结果:

线程2

线程1

线程3

调用三个线程的start方法,很明显是按照顺序调用的,但是每次运行出来的结果,基本上都不相同,随机性特别强。

怎么办呢?下面我们使用四种方案来实现。

方案一

===

我们可以利用Thread中的join方法解决线程顺序问题,下面我们来简单介绍一下join方法。

官方介绍:

Waits for this thread to die.

等待这个线程结束,也就是说当前线程等待这个线程结束后再继续执行 。

join()方法是Thread中的一个public方法,它有几个重载版本:

  • join()

  • join(long millis) //参数为毫秒

  • join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

join()方法实际是利用了wait()方法(wait方法是Object中的),只不过它不用等待notify()/notifyAll(),且不受其影响。

它结束的条件是:

  • 等待时间到

  • 目标线程已经run完(通过isAlive()方法来判断)

下面大致看看器源码:

public final void join() throws InterruptedException {

//调用了另外一个有参数的join方法

join(0);

}

public final synchronized void join(long millis) throws InterruptedException {

long base = System.currentTimeMillis();

long now = 0;

if (millis < 0) {

throw new IllegalArgumentException("timeout value is negative");

}

//0则需要一直等到目标线程run完

if (millis == 0) {

// 如果被调用join方法的线程是alive状态,则调用join的方法

while (isAlive()) {

// == this.wait(0),注意这里释放的是

//「被调用」join方法的线程对象的锁

wait(0);

}

} else {

// 如果目标线程未run完且阻塞时间未到,

//那么调用线程会一直等待。

while (isAlive()) {

long delay = millis - now;

if (delay <= 0) {

break;

}

//每次最多等待delay毫秒时间后继续争抢对象锁,获取锁后继续从这里开始的下一行执行,

//也可能提前被notify() /notifyAll()唤醒,造成delay未一次性消耗完,

//会继续执行while继续wait(剩下的delay)

wait(delay);

// 这个变量now起的不太好,叫elapsedMillis就容易理解了

now = System.currentTimeMillis() - base;

}

}

}

下面我们使用join方法来实现线程的顺序执行。

public class ThreadDemo {

public static void main(String[] args) {

final Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程1");

}

});

final Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

try {

//等待线程t1执行完成后

//本线程t2 再执行

t1.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("线程2");

}

});

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

try {

//等待线程t2执行完成后

//本线程t3 再执行

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("线程3");

}

});

t3.start();

t2.start();

t1.start();

}

}

运行结果:

线程1

线程2

线程3

不管你运行多少次上面这段代码,结果始终不变,所以,我们就解决了多个线程按照顺序执行的问题了。

下面我们来看看另外一种方案:CountDownLatch。

方案二

===

我们先来说一下CountDownLatch,然后再来使用CountDownLatch是怎么解决多个线程顺序执行的。

CountDownLatch是一种同步辅助,在AQS基础之上实现的一个并发工具类,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的语句,之前线程操作时是使用 Thread.join方法进行等待 。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经任务了,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。

下面我们就用CountDownLatch来实现多个线程顺序执行:

import java.util.concurrent.CountDownLatch;

/**

*  

* @author 小蒋学

*  CountDownLatch 实现多个线程顺序执行

*/

public class ThreadDemo {

public static void main(String[] args) {

CountDownLatch countDownLatch1 = new CountDownLatch(0);

CountDownLatch countDownLatch2 = new CountDownLatch(1);

CountDownLatch countDownLatch3 = new CountDownLatch(1);

Thread t1 = new Thread(new Work(countDownLatch1, countDownLatch2),"线程1");

Thread t2 = new Thread(new Work(countDownLatch2, countDownLatch3),"线程2");

Thread t3 = new Thread(new Work(countDownLatch3, countDownLatch3),"线程3");

t1.start();

t2.start();

t3.start();

}

static class Work implements Runnable {

CountDownLatch cOne;

CountDownLatch cTwo;

public Work(CountDownLatch cOne, CountDownLatch cTwo) {

super();

this.cOne = cOne;

this.cTwo = cTwo;

}

@Override

public void run() {

try {

cOne.await();

System.out.println("执行: " + Thread.currentThread().getName());

} catch (InterruptedException e) {

e.printStackTrace();

}finally {

cTwo.countDown();

}

}

}

}

运行结果:

执行: 线程1

执行: 线程2

执行: 线程3

关于CountDownLatch实现多个线程顺序执行就这样实现了,下面我们再用线程池来实现。

方案三

===

在Executors 类中有个单线程池的创建方式,下面我们就用单线程池的方式来实现多个线程顺序执行。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

*  

* @author 小蒋学

*  CountDownLatch 实现多个线程顺序执行

*/

public class ThreadDemo {

public static void main(String[] args) {

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程1");

}

},"线程1");

Thread t2 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程2");

}

},"线程2");

Thread t3 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("线程3");

}

});

ExecutorService executor = Executors.newSingleThreadExecutor();

// 将线程依次加入到线程池中

executor.submit(t1);

executor.submit(t2);

executor.submit(t3);

// 及时将线程池关闭

executor.shutdown();

}

}

运行结果:

线程1

线程2

线程3

这样我们利用单线程池也实现了多个线程顺序执行的问题。下面再来说一种更牛的方案。

方案四

===

最后一种方案是使用CompletableFuture来实现多个线程顺序执行。