1、通過join()的方式
子線程調用join()的時候,主線程等待子線程執行完再執行。如果讓多個線程順序執行的話,那么需要他們按順序調用start()。
/*** - 第一個迭代(i=0):* 啟動線程t1 -> 然后調用t1.join()。* 主線程(執行testMethod2的線程)會在t1.join()處阻塞,直到t1線程執行完畢。* - 第二個迭代(i=1):* 只有等到t1執行完畢,主線程才會繼續執行,然后啟動線程t2,并調用t2.join(),主線程等待t2執行完畢。* - 第三個迭代(i=2):* 同樣,主線程等待t2執行完畢后,啟動t3,然后等待t3執行完畢。* @throws InterruptedException*/@Testvoid testMethod1() throws InterruptedException {List<Thread> list = new ArrayList<>();for (int i = 1; i <= 3; i++) {Thread thread = new Thread(() -> {log.info("{} 執行~", Thread.currentThread().getName());},"t" + i);list.add(thread);}for (int i = 0; i < list.size(); i++) {list.get(i).start();//他阻塞的是主線程,當前線程執行完后,主線程才會執行list.get(i).join();}}
2、通過CountDownLatch 的方式
countDown():沒調用一次,計數器就會減 1。當計數器減到 0 時,調用await()的線程才會被喚醒。
await():當計數器不為 0 的時候,調用await()的線程會被阻塞。
/*** 1、讓第一個線程執行,完事調用countDown()再對countDownLatch記數減 1* 2、第二個線程調用await()去喚醒,但是需要線程一執行完,* 因為只有記數為 0 的話,當前線程才會被喚醒,依次類推*/@Testvoid testMethod2(){CountDownLatch countDownLatch = new CountDownLatch(1);for (int i = 1; i <= 3; i++) {final int threadId = i;new Thread(() -> {if (threadId != 1) {try {//等待計數器歸零,再繼續往下執行,否則在此處阻塞countDownLatch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}log.info("{} 執行~", Thread.currentThread().getName());//計數器減 1countDownLatch.countDown();},"t" + i).start();}}
3、通過Semaphore的方式
通過信號量控制,每個線程在開始執行前需要獲取一個許可,執行完畢后釋放下一個線程需要的許可。
/*** 只讓第一個信號量有一個許可,另外兩個信號量沒有許可* 1、第一個線程獲取到許可,執行打印,然后釋放下一個信號量的許可* 2、第二個線程獲取到許可,執行打印,然后釋放下一個信號量的許可* 3、第三個線程獲取到許可,執行打印*/@Testvoid testMethod3(){int length = 3;List<Semaphore> semaphoreList = new ArrayList<>();for (int i = 1; i <= length; i++) {Semaphore semaphore = new Semaphore(i == 1 ? 1 : 0);semaphoreList.add(semaphore);}for (int j = 0; j < length; j++) {final int theadIndex = j;new Thread(() -> {try {//獲取許可semaphoreList.get(theadIndex).acquire();log.info("{} 執行~", Thread.currentThread().getName());} catch (InterruptedException e) {throw new RuntimeException(e);}finally {//釋放許可if (theadIndex != length - 1) {semaphoreList.get(theadIndex + 1).release();}}},"t" + (j + 1)).start();}}
4、CompletableFuture的方式
原理:異步任務鏈式調用
優勢:代碼簡潔,內置線程池管理
CompletableFuture.runAsync(() -> System.out.println("t1")).thenRun(() -> System.out.println("t2")).thenRun(() -> System.out.println("t3")).join(); // 阻塞等待全部完成
5、利用線程池
原理:所有任務提交到單線程隊列順序執行
優勢:自動管理線程生命周期
注意:實際是同一個線程執行所有任務
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("t1"));
executor.submit(() -> System.out.println("t2"));
executor.submit(() -> System.out.println("t3"));
executor.shutdown();