Java中的線程同步全面講解
前言
線程同步是多線程編程的核心概念,用于協(xié)調(diào)多個線程對共享資源的訪問,防止數(shù)據(jù)不一致和并發(fā)問題。下面我將全面講解 Java 中的線程同步機制。
一、為什么需要線程同步?
當多個線程訪問共享資源時,可能出現(xiàn):
- 競態(tài)條件:多個線程同時修改同一數(shù)據(jù)
- 內(nèi)存可見性問題:一個線程修改數(shù)據(jù)后,其他線程看不到最新值
- 指令重排序問題:編譯器/處理器優(yōu)化導致代碼執(zhí)行順序改變
二、Java 同步機制分類
1. 內(nèi)置鎖(synchronized)
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代碼塊
public void update() {
synchronized(this) {
// 臨界區(qū)代碼
}
}
// 靜態(tài)方法鎖(類級別鎖)
public static synchronized void staticMethod() {
// ...
}
特點:
- 自動獲取/釋放鎖(進入同步塊獲取,退出釋放)
- 可重入(同一線程可重復獲取同一把鎖)
- 非公平鎖(不保證等待時間最長的線程先獲取鎖)
2. 顯式鎖(Lock API)
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 手動加鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 必須手動釋放鎖
}
}
Lock 接口優(yōu)勢:
- 可中斷鎖(
lockInterruptibly()) - 超時獲取鎖(
tryLock(long time, TimeUnit unit)) - 公平鎖選項(
new ReentrantLock(true)) - 多條件變量(
Condition)
3. 原子變量(Atomic Classes)
private AtomicInteger count = new AtomicInteger(0);
public void safeIncrement() {
count.incrementAndGet(); // 原子操作
}
常用原子類:
AtomicInteger,AtomicLongAtomicReferenceAtomicBooleanLongAdder(高并發(fā)計數(shù)器)
4. volatile 關(guān)鍵字
private volatile boolean running = true;
public void stop() {
running = false; // 寫操作立即對其他線程可見
}
適用場景:
- 狀態(tài)標志(單個寫入者)
- 雙重檢查鎖定模式
- 不保證復合操作的原子性
三、高級同步工具
1. 讀寫鎖(ReadWriteLock)
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public String readData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
public void writeData(String value) {
writeLock.lock();
try {
data = value;
} finally {
writeLock.unlock();
}
}
2. 條件變量(Condition)
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 等待隊列非滿
}
queue.enqueue(item);
notEmpty.signal(); // 喚醒等待的消費者
} finally {
lock.unlock();
}
}
3. 同步集合
// 并發(fā)Map Map<String, String> concurrentMap = new ConcurrentHashMap<>(); // 阻塞隊列 BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100); // 寫時復制列表 List<String> safeList = new CopyOnWriteArrayList<>();
四、線程協(xié)調(diào)工具
1. CountDownLatch(一次性門閂)
CountDownLatch latch = new CountDownLatch(3);
// 工作線程
void worker() {
// 執(zhí)行任務...
latch.countDown();
}
// 主線程
latch.await(); // 阻塞直到計數(shù)歸零
System.out.println("所有任務完成");
2. CyclicBarrier(循環(huán)屏障)
CyclicBarrier barrier = new CyclicBarrier(4, () ->
System.out.println("所有玩家準備就緒"));
void player() {
prepare();
barrier.await(); // 等待其他玩家
startGame();
}
3. Semaphore(信號量)
Semaphore semaphore = new Semaphore(5); // 5個許可證
void accessResource() {
semaphore.acquire(); // 獲取許可
try {
// 使用資源
} finally {
semaphore.release(); // 釋放許可
}
}
4. Exchanger(數(shù)據(jù)交換器)
Exchanger<String> exchanger = new Exchanger<>(); // 線程A String dataA = "Data from A"; String received = exchanger.exchange(dataA); // 線程B String dataB = "Data from B"; String received = exchanger.exchange(dataB);
五、避免死鎖的策略
1. 死鎖產(chǎn)生的必要條件
- 互斥條件
- 持有并等待
- 不可搶占
- 循環(huán)等待
2. 預防死鎖的方法
// 1. 固定鎖順序
public void transfer(Account from, Account to, int amount) {
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
synchronized(first) {
synchronized(second) {
// 轉(zhuǎn)賬操作
}
}
}
// 2. 嘗試獲取鎖(帶超時)
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 操作
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 3. 使用開放調(diào)用(避免在持有鎖時調(diào)用外部方法)
六、同步性能優(yōu)化
1. 減少鎖競爭
- 縮小同步范圍:同步代碼塊 > 同步方法
- 降低鎖粒度:使用多個鎖代替單個鎖
- 使用讀寫鎖:區(qū)分讀/寫操作
- 無鎖數(shù)據(jù)結(jié)構(gòu):原子變量、CAS操作
2. 鎖消除與鎖粗化
// 鎖消除(JIT編譯器優(yōu)化)
public String concat(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1); // 同步方法但可消除鎖
sb.append(s2);
sb.append(s3);
return sb.toString();
}
// 鎖粗化(減少頻繁加鎖開銷)
synchronized(lock) {
operation1();
operation2();
operation3();
}
3. 并發(fā)設(shè)計模式
// 1. 生產(chǎn)者-消費者模式
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
// 2. 線程局部存儲
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 3. Future模式
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> computeExpensiveValue());
// ...其他操作
Integer result = future.get();
七、Java內(nèi)存模型(JMM)與同步
Happens-Before 規(guī)則
- 程序順序規(guī)則
- 監(jiān)視器鎖規(guī)則
- volatile變量規(guī)則
- 線程啟動規(guī)則
- 線程終止規(guī)則
- 中斷規(guī)則
- 終結(jié)器規(guī)則
- 傳遞性
// 正確同步示例
class SafePublication {
private int value;
private volatile boolean initialized;
public void initialize(int val) {
value = val;
initialized = true; // volatile寫
}
public int getValue() {
if (initialized) { // volatile讀
return value;
}
return -1;
}
}
八、現(xiàn)代同步實踐
1. CompletableFuture(異步編程)
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> process(data))
.thenAccept(result -> store(result))
.exceptionally(ex -> handleError(ex));
2. StampedLock(樂觀讀鎖)
private final StampedLock lock = new StampedLock();
private double balance;
public double readBalance() {
long stamp = lock.tryOptimisticRead(); // 樂觀讀
double currentBalance = balance;
if (!lock.validate(stamp)) { // 檢查是否被修改
stamp = lock.readLock(); // 退化為悲觀讀
try {
currentBalance = balance;
} finally {
lock.unlockRead(stamp);
}
}
return currentBalance;
}
3. VarHandle(Java 9+)
class AtomicCounter {
private volatile int count;
private static final VarHandle COUNT_HANDLE;
static {
try {
COUNT_HANDLE = MethodHandles.lookup()
.findVarHandle(AtomicCounter.class, "count", int.class);
} catch (Exception e) {
throw new Error(e);
}
}
public void increment() {
int current;
do {
current = (int) COUNT_HANDLE.getVolatile(this);
} while (!COUNT_HANDLE.compareAndSet(this, current, current + 1));
}
}
九、同步機制選擇指南
| 場景 | 推薦方案 | 說明 |
|---|---|---|
| 簡單同步 | synchronized | 開發(fā)簡單,自動管理 |
| 復雜鎖控制 | ReentrantLock | 支持超時、中斷等 |
| 讀多寫少 | ReentrantReadWriteLock | 提高讀并發(fā)性能 |
| 計數(shù)器 | AtomicInteger/LongAdder | 無鎖高性能 |
| 狀態(tài)標志 | volatile | 輕量級可見性保證 |
| 線程協(xié)作 | CountDownLatch/CyclicBarrier | 協(xié)調(diào)多線程執(zhí)行 |
| 資源池 | Semaphore | 控制并發(fā)訪問數(shù)量 |
| 異步編程 | CompletableFuture | 函數(shù)式異步處理 |
十、常見同步錯誤示例
1. 誤用 String 鎖
// 錯誤!字符串常量池導致意外共享鎖
synchronized("LOCK") {
// ...
}
2. 同步方法調(diào)用非同步方法
class Account {
private int balance;
public synchronized void transfer(Account target, int amount) {
this.balance -= amount;
target.deposit(amount); // 未同步!可能破壞不變性條件
}
public void deposit(int amount) {
balance += amount;
}
}
3. 對象逃逸
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener( // 在構(gòu)造完成前發(fā)布this引用
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
void doSomething(Event e) { ... }
}
總結(jié)
Java 線程同步要點:
- 理解問題本質(zhì):解決共享資源訪問沖突
- 選擇合適的工具:從簡單到復雜逐步考慮
- 遵循最佳實踐:
- 優(yōu)先使用并發(fā)工具包(
java.util.concurrent) - 避免過度同步
- 最小化同步范圍
- 使用線程安全的集合類
- 優(yōu)先使用并發(fā)工具包(
- 考慮性能影響:
- 無鎖算法 > 樂觀鎖 > 細粒度鎖 > 粗粒度鎖
- 讀寫分離提高并發(fā)性
- 利用現(xiàn)代特性:
- CompletableFuture 異步編程
- VarHandle 精細內(nèi)存控制
- Virtual Threads(Project Loom)減少同步需求
正確使用同步機制能構(gòu)建出安全高效的多線程應用,而錯誤使用可能導致性能問題或難以調(diào)試的并發(fā)缺陷。始終優(yōu)先考慮使用高級并發(fā)工具而非手動實現(xiàn)同步邏輯。
到此這篇關(guān)于Java中線程同步的文章就介紹到這了,更多相關(guān)java線程同步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基礎(chǔ)之switch分支結(jié)構(gòu)詳解
這篇文章主要介紹了Java基礎(chǔ)之switch分支結(jié)構(gòu)詳解,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有很大的幫助,需要的朋友可以參考下2021-05-05
Hibernate處理多對多關(guān)系的實現(xiàn)示例
本文介紹了Hibernate中實現(xiàn)多對多關(guān)系的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2026-01-01
Java中@DateTimeFormat @JsonFormat失效原因及測試填坑
本文主要介紹了Java中@DateTimeFormat @JsonFormat失效原因及測試填坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06

