Java并發(fā)之線程同步、死鎖、多線程編排全面解析
線程同步的方式有哪些?
線程同步
線程同步,是多線程編程中的一種機制,用于協(xié)調(diào)多個線程的執(zhí)行順序,確保它們在共享資源或關(guān)鍵操作上按照預(yù)定的規(guī)則運行,避免因并發(fā)訪問導(dǎo)致的數(shù)據(jù)不一致、競態(tài)條件(Race Condition)等問題。
線程同步的方式有哪些?
synchronized關(guān)鍵字,通過 JVM 內(nèi)置的鎖機制實現(xiàn)線程同步,確保同一時刻只有一個線程訪問共享資源。既可以修飾實例方法也可以修飾靜態(tài)方法,也可以鎖代碼塊和鎖住某個具體的實例對象。
public synchronized void method() { ... } // 實例方法鎖(this)
public static synchronized void method() { ... } // 類方法鎖(Class 對象)
// 鎖實例對象
synchronized (lockObject) {
// 同步代碼塊
}ReentrantLock基于java.util.concurrent.locks.Lock接口實現(xiàn)的可重入互斥鎖,需顯式調(diào)用lock()和unlock()進行加鎖和解鎖。
支持公平鎖、可中斷鎖、超時鎖以及多條件變量(Condition),相比 synchronized 提供了更高的靈活性。
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 同步代碼
} finally {
lock.unlock();
}Semaphore(信號量),允許多個線程同時訪問資源,但是限制訪問線程的數(shù)量。
Semaphore semaphore = new Semaphore(3); // 初始許可數(shù)為3
semaphore.acquire(); // 獲取許可
try {
// 同步代碼
} finally {
semaphore.release(); // 釋放許可
}CountDownLatch,允許多個線程等待其他線程執(zhí)行完畢之后再執(zhí)行,用于線程間的協(xié)作。
public class LatchDemo {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 1; i <= threadCount; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 初始化完成");
latch.countDown(); // 子線程完成,計數(shù)器減 1
}, "線程-" + i).start();
}
latch.await(); // 主線程等待所有子線程完成
System.out.println("所有子線程完成,主線程繼續(xù)執(zhí)行");
}
}CyclicBarrier,多個線程互相等待,所有線程都到到屏障點后,再繼續(xù)執(zhí)行。線程計數(shù)器可重置。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierExample {
// 總和變量(線程安全)
private static int sum = 0;
// 線程池
private static final ExecutorService executor = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
// 定義需要等待的線程數(shù)量(5個)
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
// 所有線程到達屏障后執(zhí)行的回調(diào)(匯總結(jié)果)
System.out.println("所有線程已完成計算,總和為: " + sum);
});
// 啟動5個線程
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
try {
// 模擬線程計算
int value = (int) (Math.random() * 100);
System.out.println(Thread.currentThread().getName() + " 計算值: " + value);
// 將計算結(jié)果累加到總和中
sum += value;
// 調(diào)用await()等待其他線程到達屏障
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
// 關(guān)閉線程池
executor.shutdown();
}
}Phaser,和CyclicBarrier類似,但是支持更靈活的屏障操作,適用于復(fù)雜多階段任務(wù),支持動態(tài)注冊/注銷參與者,并可控制各參與者的階段進度,并可以控制各個參與者的到達和離開。
Phaser phaser = new Phaser(1); // 初始參與線程
phaser.bulkRegister(3); // 動態(tài)注冊3個工作線程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (int phase = 0; phase < 2; phase++) {
phaser.arriveAndAwaitAdvance(); // 階段1:等待所有線程完成階段1
// 執(zhí)行階段2任務(wù)
}
phaser.arriveAndDeregister(); // 完成并注銷
}).start();
}
// 主線程等待所有線程完成
phaser.arriveAndAwaitAdvance();- 其他,另外還有
volatile,這種能保證可見性和有序性,但不能保證原子性的關(guān)鍵字。以及基于CAS實現(xiàn)的Atomic類(無鎖同步),但是僅適用于簡單數(shù)據(jù)類型和部分操作(如 getAndAdd)。
死鎖
死鎖,通常是指在兩個或多個進程(或線程、事務(wù))在執(zhí)行過程中,因爭奪資源而陷入相互等待的狀態(tài),導(dǎo)致所有進程都無法繼續(xù)執(zhí)行。
什么情況下會產(chǎn)生死鎖?
產(chǎn)生死鎖的四個必要條件
- 互斥(Mutual Exclusion),一個資源只能被一個進程占用,其他進程必須等待其釋放。
- 占有并等待(Hold and Wait),進程在持有資源的同時,申請新的資源。
- 不可剝奪(No Preemption),資源只能由持有它的進程主動釋放,不能被強制剝奪。
- 循環(huán)等待(Circular Wait),存在一個進程環(huán),每個進程都在等待下一個進程所持有的資源。

如何解決死鎖?
上面我們已經(jīng)知道產(chǎn)生死鎖有四個必要條件,那解決死鎖,只需要破壞死鎖的這些必要條件即可。
一般從以下幾方面入手即可:
- 破壞“占有并等待”條件
- 進程或線程一次性申請所需的所有資源,否則不分配任何資源。(
可能導(dǎo)致資源利用率低,進程長期等待資源) - 進程或線程申請資源時,必須釋放已持有的所有資源。(
可能導(dǎo)致頻繁的資源釋放和重新申請,增加系統(tǒng)開銷) - 破壞“不可剝奪”條件,允許系統(tǒng)強制回收資源。(
可能中斷進程的正常執(zhí)行,導(dǎo)致數(shù)據(jù)不一致) - 破壞“循環(huán)等待”條件,要求進程或線程按順序申請資源。保證多個進程(線程)的執(zhí)行順序相同即可避免循環(huán)等待。這是最常用的解決死鎖的方法。
- 例如:事務(wù)1的執(zhí)行順序是:A->B->C,事務(wù)2的執(zhí)行順序是:C->D->A,這種情況下就容易產(chǎn)生死鎖。因為事務(wù)1占用了A,等待C,但是事務(wù)2占用了C但是等待A。因此只需要把事務(wù)2的執(zhí)行順序改成:A->D->C,這樣事務(wù)2在執(zhí)行時,會發(fā)現(xiàn)事務(wù)1占用著A呢,因此事務(wù)會先不執(zhí)行,等待事務(wù)1釋放A。

死鎖如何恢復(fù)?
回滾進程或線程,可以執(zhí)行一個或多個進程(或線程)回滾到安全狀態(tài),釋放資源。一般回滾時,要遵循按優(yōu)先級選擇(優(yōu)先級低的進程先回滾) 。按資源占用時間選擇(占用時間短的進程先回滾)。終止進程或線程,直接終止全部或部分死鎖進程(或線程),釋放資源。(可能導(dǎo)致數(shù)據(jù)丟失或事務(wù)不完整)資源剝奪,從某些進程或線程中強制回收資源分配給其他進程。超時機制,為進程或線程設(shè)置等待資源的超時時間,若超時則自動放棄請求并釋放已占資源。
數(shù)據(jù)庫中的死鎖
在操作數(shù)據(jù)庫時,如果有多個事務(wù)并發(fā)執(zhí)行,也是可能發(fā)生死鎖的。當(dāng)事務(wù)1持有資源A的鎖,但是嘗試獲取資源B的鎖,而事務(wù)2持有資源B的鎖,嘗試獲取資源A的鎖的時候,這時候就會發(fā)生死鎖的情況。
當(dāng)數(shù)據(jù)庫發(fā)生死鎖的時候,會報出來如下的錯誤:
Error updating database. Cause: ERR-CODE: [TDDL-4614][ERR EXECUTE ON MYSQL] Deadlock found when trying to get lock; The error occurred while setting parameters### SQL: update test_table set updated=now(),type_state = ? where test_num = 123
數(shù)據(jù)庫操作中如何避免死鎖?
一般對于數(shù)據(jù)庫的死鎖,主要是避免發(fā)生并發(fā)更新同一資源的操作?;蛘呖梢钥紤]保證操作的順序,比如多個事務(wù)都是先操作資源A、再操作資源B,這樣就能有效的避免死鎖。
還有一些其他優(yōu)化措施:
減少事務(wù)持有鎖的時間:盡快提交或回滾事務(wù)。
鎖粒度控制:使用行級鎖而非表級鎖,減少資源競爭。
避免嵌套事務(wù):減少循環(huán)等待的可能性。
多線程編排
在 Java 中,多線程的編排可以通過多種方式實現(xiàn),主要涉及 線程池、同步機制、并發(fā)工具類 以及 任務(wù)協(xié)調(diào)工具(如 Future、CompletableFuture)等。
CompletableFuture怎么實現(xiàn)多線程異步編排?
CompletableFuture,提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的復(fù)雜性,提供了函數(shù)式編程的能力,可以通過回調(diào)的方式處理計算結(jié)果,并且提供了轉(zhuǎn)換和組合CompletableFuture的方法。
我在【上一篇文章】提過CompletableFuture底層就是用ForkJoinPool來實現(xiàn),那么CompletableFuture如何使用來實現(xiàn)多線程任務(wù)編排的呢?
單個任務(wù)
runAsync:無返回值
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("無返回值任務(wù)執(zhí)行中");
});
supplyAsync:有返回值
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "異步任務(wù)結(jié)果";
});
System.out.println(future.get()); // 輸出: 異步任務(wù)結(jié)果
指定線程池
因為CompletableFuture默認(rèn)底層是使用的ForkJoinPool.commonPool(),但是也是可以自定義線程池,配置線程的一些指定信息。
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "自定義線程池任務(wù)";
}, executor);
兩個任務(wù)編排
thenApplyAsync:能接收上一次的執(zhí)行結(jié)果,還可以有返回值
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(result -> result + " World");// 運行結(jié)果 Hello World
thenRunAsync:不能接收上一次的執(zhí)行結(jié)果,并且也沒返回值
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
return "Hello";
}).thenRunAsync(() -> {
System.out.println("after Hello World");
});
組合多個任務(wù)編排
- 串行組合(
thenCompose),可以將前一個任務(wù)的結(jié)果傳遞給下一個任務(wù)(鏈?zhǔn)揭蕾嚕?/li>
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") .thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " World")); System.out.println(future.get()); // 輸出: Hello World
并行組合(thenCombine),將兩個獨立任務(wù)的結(jié)果進行合并
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2)
.thenAccept(System.out::println); // 輸出: Hello World
多任務(wù)并行(allOf / anyOf)
allOf:等待所有任務(wù)完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
CompletableFuture.runAsync(() -> System.out.println("Task 1")),
CompletableFuture.runAsync(() -> System.out.println("Task 2"))
);
allFutures.get(); // 等待所有任務(wù)完成
anyOf:任一任務(wù)完成即觸發(fā)
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(
CompletableFuture.supplyAsync(() -> "Task 1"),
CompletableFuture.supplyAsync(() -> "Task 2")
);
System.out.println(anyFuture.get()); // 輸出: Task 1 或 Task 2(取決于哪個先完成)
任務(wù)編排異常處理
- 捕獲異常(
exceptionally),在任務(wù)拋出異常時提供默認(rèn)信息。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new RuntimeException("失敗");
return "成功";
}).exceptionally(ex -> {
System.out.println("異常處理: " + ex.getMessage());
return "默認(rèn)值";
});
System.out.println(future.get());
- 全局處理(
handle / whenComplete)
handle:處理異常并返回新結(jié)果
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new RuntimeException("失敗");
return "成功";
}).handle((result, ex) -> {
if (ex != null) {
System.out.println("異常處理: " + ex.getMessage());
return "默認(rèn)值";
}
return result;
});
whenComplete:無論成功或失敗均執(zhí)行(不可中斷)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new RuntimeException("失敗");
return "成功";
}).whenComplete((result, ex) -> {
if (ex != null) System.out.println("任務(wù)失敗");
else System.out.println("任務(wù)成功: " + result);
});
到此這篇關(guān)于Java并發(fā)(線程同步、死鎖、多線程編排)的文章就介紹到這了,更多相關(guān)java多線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot從3.x到4.0的分步升級保姆級實戰(zhàn)指南
Spring Boot 4.0的發(fā)布標(biāo)志著Java生態(tài)向云原生與開發(fā)效能革命的全面邁進,下面這篇文章主要介紹了Spring Boot從3.x到4.0的分步升級保姆級實戰(zhàn)指南,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-05-05
SpringMVC源碼解讀之HandlerMapping - AbstractUrlHandlerMapping系列re
這篇文章主要介紹了SpringMVC源碼解讀之HandlerMapping - AbstractUrlHandlerMapping系列request分發(fā) 的相關(guān)資料,需要的朋友可以參考下2016-02-02

