上クラスメートの Zhou はインタビュー中に次の質問に遭遇しました:
3 つのスレッド T1、T2、および T3 があります。順次実行を保証するにはどうすればよいですか?
ルーチン操作。3 つのスレッドを開始して実行します。
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
3 つのスレッドの start メソッドの呼び出しは明らかに順番に呼び出されますが、各実行の結果は基本的に異なり、ランダム性が特に強力です。 ######どうやってするの?以下では、これを達成するために 4 つのソリューションを使用します。
公式紹介:
このスレッドが終了するまで待ちます。等待这个线程结束,也就是说当前线程等待这个线程结束后再继续执行 。
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
ポーリングを使用して、タスクの実行が完了したかどうかを判断し、結果を取得します。 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
实现了多个线程顺序执行的问题。
关于多个线程顺序执行,不管是对于面试,还是工作,关于多线程顺序执行的解决方案都是非常有必要掌握的。也希望下次面试官再问:多线程顺序执行问题的时候,你的表情应该是这样的:
以上がマルチスレッドの順次実行、2 種類しか知りませんか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。