Java中控制多線程順序執(zhí)行的六種實(shí)現(xiàn)方案
一、線程順序執(zhí)行的核心挑戰(zhàn)
在多線程編程中,線程的執(zhí)行順序本質(zhì)上是不確定的,由操作系統(tǒng)調(diào)度器決定。但在某些業(yè)務(wù)場(chǎng)景中,我們需要確保線程按照特定順序執(zhí)行,例如:
- 分階段任務(wù)處理(先初始化,再加載,最后執(zhí)行)
- 事件的有序處理(保證事件處理的先后順序)
- 資源的有序訪問(避免競(jìng)爭(zhēng)條件)
下面介紹的6種方法都能解決這個(gè)問題,但各有特點(diǎn)和適用場(chǎng)景。
二、6種實(shí)現(xiàn)方法詳解
方法1:join() - 簡(jiǎn)單直接的阻塞方案
實(shí)現(xiàn)原理:
join()方法會(huì)使當(dāng)前線程等待目標(biāo)線程執(zhí)行完畢。通過在主線程中依次調(diào)用各個(gè)線程的join(),可以實(shí)現(xiàn)嚴(yán)格的順序執(zhí)行。
public class JoinExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("第一階段任務(wù)執(zhí)行");
// 模擬任務(wù)執(zhí)行時(shí)間
try { Thread.sleep(500); } catch (InterruptedException e) {}
});
Thread t2 = new Thread(() -> System.out.println("第二階段任務(wù)執(zhí)行"));
Thread t3 = new Thread(() -> System.out.println("第三階段任務(wù)執(zhí)行"));
try {
System.out.println("啟動(dòng)第一階段任務(wù)");
t1.start();
t1.join(); // 主線程在此等待t1完成
System.out.println("啟動(dòng)第二階段任務(wù)");
t2.start();
t2.join(); // 等待t2完成
System.out.println("啟動(dòng)第三階段任務(wù)");
t3.start();
t3.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("任務(wù)執(zhí)行被中斷");
}
}
}優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡(jiǎn)單直觀
- 不需要額外同步工具
缺點(diǎn):
- 會(huì)阻塞主線程
- 不夠靈活,難以應(yīng)對(duì)復(fù)雜場(chǎng)景
適用場(chǎng)景:簡(jiǎn)單的線性任務(wù)流,且可以接受阻塞主線程的情況。
方法2:?jiǎn)尉€程線程池 - 優(yōu)雅的任務(wù)隊(duì)列方案
實(shí)現(xiàn)原理:
通過Executors.newSingleThreadExecutor()創(chuàng)建單線程的線程池,自然保證任務(wù)按提交順序執(zhí)行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
System.out.println("準(zhǔn)備數(shù)據(jù)");
// 模擬耗時(shí)操作
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
executor.execute(() -> System.out.println("處理數(shù)據(jù)"));
executor.execute(() -> System.out.println("保存結(jié)果"));
executor.shutdown();
// 等待所有任務(wù)完成
while (!executor.isTerminated()) {}
System.out.println("所有任務(wù)已完成");
}
}優(yōu)點(diǎn):
- 自動(dòng)管理線程生命周期
- 支持任務(wù)隊(duì)列
- 避免手動(dòng)創(chuàng)建線程
缺點(diǎn):
- 無法靈活控制中間狀態(tài)
- 單線程可能成為性能瓶頸
適用場(chǎng)景:需要順序執(zhí)行但可能動(dòng)態(tài)添加任務(wù)的場(chǎng)景。
方法3:CountDownLatch - 靈活的同步屏障方案
實(shí)現(xiàn)原理:
CountDownLatch通過計(jì)數(shù)器實(shí)現(xiàn)線程等待,允許一個(gè)或多個(gè)線程等待其他線程完成操作。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
// 第一個(gè)閘門,初始為1表示t1可以直接執(zhí)行
CountDownLatch latch1 = new CountDownLatch(1);
// 第二個(gè)閘門,t2需要等待
CountDownLatch latch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
System.out.println("數(shù)據(jù)庫連接建立");
latch1.countDown(); // t1完成后打開閘門1
});
Thread t2 = new Thread(() -> {
try {
latch1.await(); // 等待閘門1打開
System.out.println("查詢用戶數(shù)據(jù)");
latch2.countDown(); // t2完成后打開閘門2
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t3 = new Thread(() -> {
try {
latch2.await(); // 等待閘門2打開
System.out.println("生成報(bào)表");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 故意打亂啟動(dòng)順序測(cè)試
t3.start();
t2.start();
t1.start();
}
}優(yōu)點(diǎn):
- 靈活控制多個(gè)線程的依賴關(guān)系
- 支持一對(duì)多、多對(duì)多同步
- 不阻塞主線程
缺點(diǎn):
- 需要?jiǎng)?chuàng)建多個(gè)CountDownLatch對(duì)象
- 計(jì)數(shù)器不可重置
適用場(chǎng)景:分階段任務(wù),特別是多階段有復(fù)雜依賴關(guān)系的場(chǎng)景。
方法4:ReentrantLock與Condition - 精準(zhǔn)控制的等待/通知機(jī)制
實(shí)現(xiàn)原理:
ReentrantLock配合Condition提供了比synchronized更靈活的線程通信機(jī)制。每個(gè)Condition對(duì)象實(shí)質(zhì)上是一個(gè)獨(dú)立的等待隊(duì)列,可以實(shí)現(xiàn)精確的線程喚醒控制。
import java.util.concurrent.locks.*;
public class ReentrantLockConditionExample {
// 可重入鎖
private static Lock lock = new ReentrantLock(true); // 公平鎖
// 三個(gè)條件變量
private static Condition condition1 = lock.newCondition();
private static Condition condition2 = lock.newCondition();
private static Condition condition3 = lock.newCondition();
// 狀態(tài)標(biāo)志
private static volatile int flag = 1;
public static void main(String[] args) {
Thread t1 = new Thread(() -> task(1, 2, condition1, condition2));
Thread t2 = new Thread(() -> task(2, 3, condition2, condition3));
Thread t3 = new Thread(() -> task(3, 1, condition3, condition1));
t1.start();
t2.start();
t3.start();
// 模擬運(yùn)行后停止
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
private static void task(int currentFlag, int nextFlag,
Condition waitCondition, Condition signalCondition) {
while (true) {
lock.lock();
try {
// 檢查是否輪到自己執(zhí)行
while (flag != currentFlag) {
waitCondition.await(); // 釋放鎖并等待
}
System.out.println("Thread " + currentFlag + " 正在執(zhí)行");
Thread.sleep(1000); // 模擬處理時(shí)間
// 更新狀態(tài)并喚醒下一個(gè)線程
flag = nextFlag;
signalCondition.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
}關(guān)鍵點(diǎn)解析:
- 公平鎖:構(gòu)造
ReentrantLock時(shí)傳入true參數(shù)創(chuàng)建公平鎖,減少線程饑餓 - 條件變量:每個(gè)線程有自己專屬的
Condition,避免無效喚醒 - volatile變量:確保狀態(tài)標(biāo)志的可見性
- await/signal:精確控制線程的等待和喚醒
優(yōu)點(diǎn):
- 最精確的線程控制能力
- 支持公平性配置
- 可中斷的等待機(jī)制
- 支持多個(gè)等待條件
缺點(diǎn):
- 實(shí)現(xiàn)復(fù)雜度高
- 需要手動(dòng)管理鎖的獲取和釋放
- 容易遺漏unlock導(dǎo)致死鎖
適用場(chǎng)景:
- 需要精確控制線程執(zhí)行順序的復(fù)雜場(chǎng)景
- 多條件等待的線程協(xié)作
- 對(duì)公平性有要求的場(chǎng)景
方法5:Semaphore - 基于許可的同步控制
實(shí)現(xiàn)原理:
Semaphore通過維護(hù)一組許可(permits)來控制線程訪問。初始化時(shí)指定許可數(shù)量,線程通過acquire()獲取許可,通過release()釋放許可。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 初始化信號(hào)量:t1可以直接運(yùn)行,t2和t3需要等待
private static Semaphore s1 = new Semaphore(1);
private static Semaphore s2 = new Semaphore(0);
private static Semaphore s3 = new Semaphore(0);
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
s1.acquire(); // 獲取許可
System.out.println("線程1:數(shù)據(jù)加載");
Thread.sleep(1000);
s2.release(); // 釋放t2的許可
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t2 = new Thread(() -> {
try {
s2.acquire();
System.out.println("線程2:數(shù)據(jù)處理");
Thread.sleep(1000);
s3.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t3 = new Thread(() -> {
try {
s3.acquire();
System.out.println("線程3:結(jié)果保存");
Thread.sleep(1000);
s1.release(); // 循環(huán)執(zhí)行時(shí)可重新開始
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 啟動(dòng)線程
t1.start();
t2.start();
t3.start();
// 允許程序運(yùn)行一段時(shí)間后退出
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}進(jìn)階用法:
- 公平模式:
new Semaphore(1, true) - 批量獲取:
acquire(int permits) - 非阻塞獲取:
tryAcquire() - 超時(shí)控制:
tryAcquire(long timeout, TimeUnit unit)
優(yōu)點(diǎn):
- 靈活控制并發(fā)度
- 支持許可的申請(qǐng)和釋放
- 可實(shí)現(xiàn)資源池等復(fù)雜模式
缺點(diǎn):
- 需要仔細(xì)設(shè)計(jì)許可數(shù)量
方法6:CompletableFuture - 函數(shù)式異步編程
實(shí)現(xiàn)原理:
CompletableFuture是Java 8引入的增強(qiáng)版Future,支持函數(shù)式編程風(fēng)格的任務(wù)編排。通過thenRun、thenApply等方法鏈?zhǔn)浇M合多個(gè)異步任務(wù)。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
// 創(chuàng)建異步任務(wù)鏈
CompletableFuture<Void> taskChain = CompletableFuture
.runAsync(() -> {
System.out.println("任務(wù)1:初始化系統(tǒng)");
sleep(1000);
})
.thenRunAsync(() -> {
System.out.println("任務(wù)2:加載配置");
sleep(1500);
})
.thenRunAsync(() -> {
System.out.println("任務(wù)3:?jiǎn)?dòng)服務(wù)");
sleep(500);
})
.thenRunAsync(() -> {
System.out.println("任務(wù)4:運(yùn)行監(jiān)控");
});
// 等待所有任務(wù)完成
try {
taskChain.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
private static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}優(yōu)點(diǎn):
- 聲明式編程風(fēng)格
- 強(qiáng)大的任務(wù)組合能力
- 內(nèi)置異常處理機(jī)制
- 與現(xiàn)代Java特性完美集成
缺點(diǎn):
- 學(xué)習(xí)曲線較陡峭
- 調(diào)試相對(duì)困難
- Java 8+才支持
適用場(chǎng)景:
- 現(xiàn)代Java應(yīng)用開發(fā)
- 復(fù)雜的異步任務(wù)編排
- 需要組合多個(gè)異步結(jié)果的場(chǎng)景
- 響應(yīng)式編程基礎(chǔ)
三、常見問題與解決方案
Q1:為什么await()要在while循環(huán)中調(diào)用?
A:這是為了防止"虛假喚醒"(spurious wakeup),即線程可能在沒有收到通知的情況下被喚醒。while循環(huán)會(huì)重新檢查條件,確保條件真正滿足。
Q2:如何避免死鎖?
A:遵循以下原則:
- 按固定順序獲取多個(gè)鎖
- 設(shè)置鎖超時(shí)時(shí)間
- 避免在鎖中調(diào)用外部方法
- 使用tryLock()替代lock()
以上就是Java中控制多線程順序執(zhí)行的六種實(shí)現(xiàn)方案的詳細(xì)內(nèi)容,更多關(guān)于Java控制多線程順序執(zhí)行的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA導(dǎo)入項(xiàng)目報(bào)錯(cuò)java程序包不存在問題及解決
在IDEA導(dǎo)入項(xiàng)目時(shí),若出現(xiàn)javafx包不存在錯(cuò)誤,需檢查并更改為JDK1.8版本(原默認(rèn)為JDK14),同時(shí)確保模塊目錄正確、Tomcat配置無誤,并補(bǔ)全JavaFX依賴,即可解決運(yùn)行問題2025-09-09
SpringBoot后端實(shí)現(xiàn)小程序微信登錄功能實(shí)現(xiàn)
微信小程序登錄是開發(fā)者通過微信提供的身份驗(yàn)證機(jī)制,獲取用戶唯一標(biāo)識(shí)(openid)和會(huì)話密鑰(session_key)的過程,這篇文章給大家介紹SpringBoot后端實(shí)現(xiàn)小程序微信登錄功能實(shí)現(xiàn),感興趣的朋友跟隨小編一起看看吧2025-05-05
SpringBoot WebSocket實(shí)時(shí)監(jiān)控異常的詳細(xì)流程
最近做了一個(gè)需求,消防的設(shè)備巡檢,如果巡檢發(fā)現(xiàn)異常,通過手機(jī)端提交,后臺(tái)的實(shí)時(shí)監(jiān)控頁面實(shí)時(shí)獲取到該設(shè)備的信息及位置,然后安排員工去處理。這篇文章主要介紹了SpringBoot WebSocket實(shí)時(shí)監(jiān)控異常的全過程,感興趣的朋友一起看看吧2021-10-10
使用Java 壓縮文件打包tar.gz 包的詳細(xì)教程
本文帶領(lǐng)大家學(xué)習(xí)如何使用Java 壓縮文件打包tar.gz 包,主要通過 Apache compress 工具打包,通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2021-05-05
從零搭建SpringBoot+MyBatisPlus快速開發(fā)腳手架
這篇文章主要為大家介紹了從零搭建SpringBoot+MyBatisPlus快速開發(fā)腳手架示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
使用Spring的FactoryBean創(chuàng)建和獲取Bean對(duì)象方式
這篇文章主要介紹了使用Spring的FactoryBean創(chuàng)建和獲取Bean對(duì)象方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Nacos框架服務(wù)注冊(cè)實(shí)現(xiàn)流程
這篇文章主要介紹了SpringCloud服務(wù)注冊(cè)之nacos實(shí)現(xiàn)過程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
Activiti7與Spring以及Spring Boot整合開發(fā)
這篇文章主要介紹了Activiti7與Spring以及Spring Boot整合開發(fā),在Activiti中核心類的是ProcessEngine流程引擎,與Spring整合就是讓Spring來管理ProcessEngine,有感興趣的同學(xué)可以參考閱讀2023-03-03
servlet基礎(chǔ)知識(shí)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了servlet基礎(chǔ)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07

