La semaine dernière, un camarade de classe a rencontré une telle question lors de l'interview :
Il y a trois fils de discussion T1, T2 et T3. Comment assurer une exécution séquentielle ?
Fonctionnement normal, démarrez trois threads et laissez-les s'exécuter.
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(); } }
Résultats d'exécution :
线程2 线程1 线程3
Appelez les méthodes de démarrage des trois threads. Évidemment, elles sont appelées dans l'ordre, mais les résultats de chaque exécution sont fondamentalement différents et le caractère aléatoire est particulièrement fort.
Que faire ? Ci-dessous, nous utilisons quatre solutions pour y parvenir.
Nous pouvons utiliser la méthode join dans Thread pour résoudre le problème de l'ordre des threads. Présentons brièvement la méthode join.
Introduction officielle :
J'attends que ce fil meure.
等待这个线程结束,也就是说当前线程等待这个线程结束后再继续执行 。
join()
方法是Thread
中的一个public
方法,它有几个重载版本:
join()
join(long millis)
//参数为毫秒join(long millis,int nanoseconds)
//第一参数为毫秒,第二个参数为纳秒join()方法实际是利用了wait()
方法(wait方法是Object中的),只不过它不用等待notify()/notifyAll()
,且不受其影响。
它结束的条件是:
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
轮询以Goyave
类库ListenableFuture
、FutureCallback
。(netty也有类似的实现)Java 8 CompletableFuture
弥补了Java在异步编程方面的弱势。Future isDone
轮询以判断任务是否执行结束,并获取结果。
Guava
类库ListenableFuture
、FutureCallback
。(netty也有类似的实现)Java 8 CompletableFuture
弥补了Java在异步编程方面的弱势。
在Java中异步编程,不一定非要使用rxJava,Java本身的库中的
CompletableFuture
可以很好的应对大部分的场景。
Java8
新增的CompletableFuture
在Java中异步编程,不一定非要使用rxJava,Java本身的库中的CompletableFuture
可以很好的应对大部分的场景。
Java8
新增的使用<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">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
实现了多个线程顺序执行的问题。
关于多个线程顺序执行,不管是对于面试,还是工作,关于多线程顺序执行的解决方案都是非常有必要掌握的。也希望下次面试官再问:多线程顺序执行问题的时候,你的表情应该是这样的:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!