前几天有一位同事在阿里一面的时候被问到这么一个多线程问题:如何保证多个线程的顺序执行。当时他没有回答上来,所以为了避免大家以后遇到同样的面试题还是回答不上来,今天我们就来分析解答下这个问题。
问题缘由
由于线程执行是靠CPU分时间片来处理的,那么多个线程执行的时候,如果不加限制,那么线程的执行顺序是无法保证的。如下源码:
public class OrderThreadMain { public static void main(String[] args) { Thread A = new Thread(()->{ System.out.println("A"); }); Thread B = new Thread(()->{ System.out.println("B"); }); Thread C = new Thread(()->{ System.out.println("C"); }); A.start(); B.start(); C.start(); }}
A B C三个线程同时启动,最后的执行结果不是每次都顺序输出 A B C,而是每次运行结果都是不一样的。有可能输出A B C,有也可能输出 B A C,是无法保证线程的顺序执行的。
join方式实现
join方式即使用Thread.join方法来实现。Thread.join含义当前线程需要等待previousThread线程终止之后才从thread.join返回。简单来说,就是线程没有执行完之前,会一直阻塞在join方法处。
join方式实现方式存在两种:主线程join和执行线程join。下面我们依次来分析一下:
1.主线程join
public class OrderThreadMain { public static void main(String[] args) throws InterruptedException { Thread A = new Thread(()->{ System.out.println("A"); }); Thread B = new Thread(()->{ System.out.println("B"); }); Thread C = new Thread(()->{ System.out.println("C"); }); A.start(); //等待A线程执行完在启动B线程 A.join(); B.start(); //等待B线程执行完在启动C线程 B.join(); C.start(); C.join(); }}//输出结果://A //B//C
上面源码就是主线程join的实现方式,其原理就是保证执行线程执行完毕再start后续线程,从而实现多个线程的顺序执行。
2.执行线程join
package com.example.demo.concurrent;import java.util.concurrent.Executors;public class OrderThreadMain { static class TestThread extends Thread{ private Thread beforeThread; private String str; TestThread(Thread beforeThread, String str){ this.beforeThread = beforeThread; this.str = str; } @Override public void run() { if (beforeThread != null){ try { beforeThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(str); } } public static void main(String[] args) { TestThread A = new TestThread(null, "A"); TestThread B = new TestThread(A, "B"); TestThread C = new TestThread(B, "C"); A.start(); B.start(); C.start(); }}//输出结果://A //B//C
上面源码就是执行线程join的实现方式,其原理就是通过传入beforeThread(在这个线程执行前需要执行完的线程对象)来保证多个线程顺序执行。
Thread.join源码实现:
public final void join() throws InterruptedException { 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"); } if (millis == 0) { //一直轮训线程是否执行完毕,执行完毕则结束,执行未完毕一直轮训 while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
由Thread.join源码可以知道,Thread.join原理其实很简单,就是如果线程还未执行完成就一直轮训等待,执行完成则结束轮训,继续执行后续代码。
Executors.newSingleThreadExecutor方式实现
Executors.newSingleThreadExecutor是一种特殊的线程池实现,它是一个单线程的线程池,核心线程数和最大线程数都为1,相当于串行执行,所以可以通过它来实现多个线程顺序执行。
注:如果大家对线程池不是很了解可以阅读作者之前一篇关于线程池的文章:
阿里P8大佬总结:Java线程池详解,看了你就懂
public class OrderThreadMain { public static void main(String[] args) { Runnable A = new Runnable() { @Override public void run() { System.out.println("A"); } }; Runnable B = new Runnable() { @Override public void run() { System.out.println("B"); } }; Runnable C = new Runnable() { @Override public void run() { System.out.println("C"); } }; ExecutorService executorService = Executors.newSingleThreadExecutor(); //按顺序提交任务 可以保证多个线程按提交顺序执行 executorService.submit(A); executorService.submit(B); executorService.submit(C); }}//输出结果://A //B//C
上面源码就是Executors.newSingleThreadExecutor的实现方式,只要保证任务时按顺序提交,那么就能保证多个线程任务的顺序执行。
END
笔者是一位热爱互联网、热爱互联网技术、热于分享的年轻人,如果您跟我一样,我愿意成为您的朋友,分享每一个有价值的知识给您。喜欢作者的同学,点赞+转发+关注哦!
点赞+转发+关注,私信作者“读书笔记”即可获得BAT大厂面试资料、高级架构师VIP视频课程等高质量技术资料。