您好,欢迎访问一九零五行业门户网

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

故事 上周一位同学在面试中遇到了这么一道问题:
有三个线程t1、t2、t3,如何保证顺序执行?
常规操作,启动三个线程,让其执行。
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() { 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来实现多个线程顺序执行。
在java 8问世前想要实现任务的回调,一般有以下两种方式:
借助future isdone轮询以判断任务是否执行结束,并获取结果。借助guava类库listenablefuture、futurecallback。(netty也有类似的实现)java 8 completablefuture弥补了java在异步编程方面的弱势。
在java中异步编程,不一定非要使用rxjava,java本身的库中的completablefuture可以很好的应对大部分的场景。
java8新增的completablefuture则借鉴了netty等对future的改造,简化了异步编程的复杂性,并且提供了函数式编程的能力 。
使用future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isdone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。
从java 8开始引入了completablefuture,它针对future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
接下来我们就使用completablefuture来实现多个线程顺序执行。
import java.util.concurrent.completablefuture;/** * 公众号:面试专栏 * @author 小蒋学 * countdownlatch 实现多个线程顺序执行 */public class threaddemo { public static void main(string[] args) { thread t1 = new thread(new work(),"线程1"); thread t2 = new thread(new work(),"线程2"); thread t3 = new thread(new work(),"线程3"); completablefuture.runasync(()-> t1.start()) .thenrun(()->t2.start()) .thenrun(()->t3.start()); } static class work implements runnable{ @override public void run() { system.out.println("执行 : " + thread.currentthread().getname()); } }}
运行结果:
执行 : 线程1执行 : 线程2执行 : 线程3
到此,我们就使用completablefuture实现了多个线程顺序执行的问题。
总结 关于多个线程顺序执行,不管是对于面试,还是工作,关于多线程顺序执行的解决方案都是非常有必要掌握的。也希望下次面试官再问:多线程顺序执行问题的时候,你的表情应该是这样的:
以上就是多线程顺序执行,只知道两种?的详细内容。
其它类似信息

推荐信息