Java中 synchronized 和 volatile的核心區(qū)別解析
概述
Java 并發(fā)編程中的兩個(gè)核心關(guān)鍵字:synchronized 和 volatile。它們都是為了解決多線程環(huán)境下的數(shù)據(jù)一致性問題,但在作用機(jī)制、保證的特性以及適用場(chǎng)景上有著本質(zhì)的區(qū)別。
簡(jiǎn)單來(lái)說(shuō):
synchronized 是一把“重量級(jí)的鎖”,它通過(guò)互斥訪問來(lái)保證原子性、可見性和有序性。
volatile 是一個(gè)“輕量級(jí)的同步機(jī)制”,它主要保證可見性和有序性,但不保證原子性。
1. synchronized 關(guān)鍵字詳解
synchronized 是 Java 中最基礎(chǔ)、最常用的同步機(jī)制,它通過(guò)獲取和釋放對(duì)象的“監(jiān)視器鎖”(Monitor Lock)來(lái)實(shí)現(xiàn)線程間的互斥訪問。
1.1 作用與核心特性
- 互斥性 (Mutual Exclusion)
這是 synchronized 最核心的作用。它確保在同一時(shí)刻,只有一個(gè)線程能夠執(zhí)行被 synchronized 保護(hù)的代碼塊或方法。其他試圖進(jìn)入的線程會(huì)被阻塞,直到當(dāng)前線程釋放鎖。 - 原子性 (Atomicity)
由于互斥性,被 synchronized 保護(hù)的代碼塊被視為一個(gè)不可分割的整體。線程要么執(zhí)行完整個(gè)代碼塊,要么完全不執(zhí)行,不會(huì)被其他線程打斷。這保證了復(fù)合操作(如 i++)的原子性。 - 可見性 (Visibility)
synchronized 不僅提供互斥,還保證了內(nèi)存可見性。根據(jù) Java 內(nèi)存模型 (JMM) 的規(guī)定:
進(jìn)入 synchronized 塊時(shí):線程會(huì)清空其工作內(nèi)存中共享變量的副本,強(qiáng)制從主內(nèi)存重新加載最新的值。退出 synchronized 塊時(shí):線程會(huì)將其工作內(nèi)存中對(duì)共享變量的修改強(qiáng)制刷新回主內(nèi)存。
這樣,一個(gè)線程在臨界區(qū)內(nèi)對(duì)變量的修改,對(duì)下一個(gè)進(jìn)入該臨界區(qū)的線程是立即可見的。 - 有序性 (Ordering)
synchronized 通過(guò)“一個(gè)變量在同一時(shí)刻只允許一個(gè)線程對(duì)其進(jìn)行 lock 操作”的規(guī)則,天然地禁止了指令重排序。在 synchronized 塊內(nèi)部,代碼的執(zhí)行順序與程序的書寫順序一致。
1.2. 使用方式
synchronized 可以修飾方法或代碼塊,鎖定的對(duì)象不同,其作用范圍也不同。
1.2.1 修飾實(shí)例方法 (非靜態(tài)方法)
public class Counter {
private int count = 0;
// 鎖定的是當(dāng)前對(duì)象實(shí)例 (this)
public synchronized void increment() {
count++; // 這個(gè)操作是原子的
}
public synchronized int getCount() {
return count;
}
}鎖對(duì)象 當(dāng)前對(duì)象實(shí)例 (this)。
作用范圍 同一個(gè)對(duì)象實(shí)例的多個(gè) synchronized 實(shí)例方法之間是互斥的。不同對(duì)象實(shí)例的 synchronized 方法可以并發(fā)執(zhí)行。
1.2.2 修飾靜態(tài)方法
public class GlobalCounter {
private static int globalCount = 0;
// 鎖定的是當(dāng)前類的 Class 對(duì)象 (GlobalCounter.class)
public static synchronized void incrementGlobal() {
globalCount++;
}
public static synchronized int getGlobalCount() {
return globalCount;
}
}鎖對(duì)象 該類的 Class 對(duì)象。
作用范圍 無(wú)論創(chuàng)建多少個(gè)類的實(shí)例,所有線程在調(diào)用該類的 synchronized 靜態(tài)方法時(shí),都會(huì)競(jìng)爭(zhēng)同一把鎖,實(shí)現(xiàn)全局互斥。
1.2.3 修飾代碼塊 (Synchronized Block)
public class FineGrainedCounter {
private int countA = 0;
private int countB = 0;
private final Object lockA = new Object();
private final Object lockB = new Object();
// 只鎖定操作 countA 的部分,提高并發(fā)度
public void incrementA() {
synchronized (lockA) { // 鎖定指定的對(duì)象 lockA
countA++;
}
}
// 只鎖定操作 countB 的部分
public void incrementB() {
synchronized (lockB) { // 鎖定指定的對(duì)象 lockB
countB++;
}
}
// 鎖定當(dāng)前對(duì)象實(shí)例
public void doSomething() {
synchronized (this) {
// ... 臨界區(qū)代碼
}
}
}鎖對(duì)象 synchronized 括號(hào)內(nèi)指定的任意對(duì)象。
作用范圍 靈活性最高??梢跃_控制需要同步的代碼范圍,避免將整個(gè)方法都鎖定,從而減少鎖的競(jìng)爭(zhēng),提高并發(fā)性能。
1.3. 實(shí)現(xiàn)原理
JVM 通過(guò)對(duì)象內(nèi)部的“監(jiān)視器鎖”(Monitor)來(lái)實(shí)現(xiàn) synchronized。在字節(jié)碼層面:
- 進(jìn)入 synchronized 代碼塊時(shí),會(huì)執(zhí)行 monitorenter 指令。
- 退出 synchronized 代碼塊(正常退出或發(fā)生異常)時(shí),會(huì)執(zhí)行 monitorexit 指令。
為了優(yōu)化性能,JDK 1.6 引入了鎖升級(jí)機(jī)制:
- 無(wú)鎖狀態(tài)
- 偏向鎖 (Biased Locking)
針對(duì)只有一個(gè)線程訪問同步塊的場(chǎng)景,將鎖偏向于該線程,減少不必要的 CAS 操作。 - 輕量級(jí)鎖 (Lightweight Locking)
當(dāng)有第二個(gè)線程競(jìng)爭(zhēng)時(shí),升級(jí)為輕量級(jí)鎖,通過(guò)自旋 CAS 嘗試獲取鎖,避免線程阻塞。 - 重量級(jí)鎖 (Heavyweight Locking)
當(dāng)自旋一定次數(shù)后仍未獲取到鎖,或有多個(gè)線程競(jìng)爭(zhēng)時(shí),升級(jí)為重量級(jí)鎖,線程會(huì)被掛起,進(jìn)入阻塞狀態(tài)。
1.4. 優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
功能強(qiáng)大,能同時(shí)保證原子性、可見性和有序性。
使用簡(jiǎn)單,是解決并發(fā)問題的首選方案。
支持重入,同一個(gè)線程可以多次獲取同一把鎖。 - 缺點(diǎn)
性能開銷: 獲取和釋放鎖需要操作系統(tǒng)介入,可能導(dǎo)致線程上下文切換,帶來(lái)性能損耗。
可能導(dǎo)致死鎖: 如果多個(gè)線程以不同的順序獲取多個(gè)鎖,可能會(huì)發(fā)生死鎖。
阻塞: 未獲取到鎖的線程會(huì)被阻塞,無(wú)法做其他事情。
1.5. 適用場(chǎng)景
適用于需要對(duì)共享資源進(jìn)行復(fù)雜操作、保證操作原子性的場(chǎng)景,例如:
- 銀行轉(zhuǎn)賬(需要保證扣款和加款兩個(gè)操作的原子性)。
- 計(jì)數(shù)器的遞增 (i++)。
- 對(duì)集合進(jìn)行增刪改查操作。
2. volatile 關(guān)鍵字詳解
volatile 是一個(gè)變量修飾符,它不提供任何互斥機(jī)制,而是通過(guò)內(nèi)存屏障(Memory Barrier)來(lái)保證變量的可見性和禁止指令重排序。
2.1 作用與核心特性
- 可見性 (Visibility): 這是 volatile 最主要的作用。
- 當(dāng)一個(gè)線程修改了 volatile 變量的值,這個(gè)新值會(huì)立即被寫入主內(nèi)存。
- 當(dāng)其他線程讀取這個(gè) volatile 變量時(shí),會(huì)強(qiáng)制從主內(nèi)存中讀取最新的值,而不是使用自己工作內(nèi)存中的緩存副本。
- 這樣就保證了所有線程看到的都是該變量的最新值。
- 有序性 (Ordering) volatile 通過(guò)插入內(nèi)存屏障來(lái)禁止指令重排序。
- 在寫一個(gè) volatile 變量之前,JVM 會(huì)插入一個(gè) StoreStore 屏障,確保之前的普通寫操作都已完成。
- 在寫一個(gè) volatile 變量之后,JVM 會(huì)插入一個(gè) StoreLoad 屏障,確保寫操作對(duì)其他處理器可見。
- 在讀一個(gè) volatile 變量之前,JVM 會(huì)插入一個(gè) LoadLoad 屏障,確保讀取到的是最新值。
- 在讀一個(gè) volatile 變量之后,JVM 會(huì)插入一個(gè) LoadStore 屏障,確保后續(xù)的普通寫操作不會(huì)被重排序到讀操作之前。
- 這保證了 volatile 變量的讀寫操作不會(huì)被重排序,并且建立了“happens-before”關(guān)系。
- 不保證原子性 (No Atomicity)
- volatile 無(wú)法保證復(fù)合操作的原子性。例如,volatile int count = 0; 語(yǔ)句 count++ 看起來(lái)是一條語(yǔ)句,但在底層是“讀取-修改-寫入”三個(gè)步驟。即使 count 是 volatile 的,多個(gè)線程同時(shí)執(zhí)行 count++ 時(shí),依然可能出現(xiàn)競(jìng)態(tài)條件,導(dǎo)致最終結(jié)果小于預(yù)期。
2.2. 使用方式
volatile 只能用來(lái)修飾變量。
public class VolatileExample {
// 修飾一個(gè)布爾標(biāo)志位,用于線程間通信
private volatile boolean shutdownRequested = false;
// 修飾一個(gè)對(duì)象引用
private volatile Config config;
// 線程A:設(shè)置標(biāo)志位
public void shutdown() {
shutdownRequested = true; // 寫操作,會(huì)立即刷新到主內(nèi)存
}
// 線程B:檢查標(biāo)志位
public void doWork() {
while (!shutdownRequested) { // 讀操作,每次都從主內(nèi)存讀取最新值
// ... 執(zhí)行任務(wù)
}
// 收到關(guān)閉請(qǐng)求,優(yōu)雅退出
}
// 注意:以下操作不是原子的!
private volatile int counter = 0;
public void unsafeIncrement() {
counter++; // 讀-改-寫,非原子操作,多線程下結(jié)果可能錯(cuò)誤
}
}2.3 實(shí)現(xiàn)原理
volatile 的實(shí)現(xiàn)主要依賴于 CPU 的緩存一致性協(xié)議(如 MESI)和 JVM 插入的內(nèi)存屏障指令。它告訴 JVM 和 CPU,這個(gè)變量是“易變的”,不能對(duì)其進(jìn)行激進(jìn)的優(yōu)化(如緩存、重排序)。
2.4. 優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
輕量級(jí): 相比 synchronized,開銷非常小,不會(huì)引起線程阻塞。
保證可見性和有序性: 適用于簡(jiǎn)單的狀態(tài)標(biāo)志傳遞。 - 缺點(diǎn)
不保證原子性: 無(wú)法用于需要原子操作的場(chǎng)景。
功能有限: 只能用于變量,不能用于方法或代碼塊。
2.5. 適用場(chǎng)景
適用于“一個(gè)線程寫,多個(gè)線程讀”,且寫操作是原子的(通常是直接賦值)的場(chǎng)景:
- 狀態(tài)標(biāo)志位 如上面例子中的 shutdownRequested,用于通知其他線程停止工作。
- 一次性安全發(fā)布 (Safe Publication) 在對(duì)象構(gòu)造完成后,通過(guò) volatile 引用發(fā)布,可以保證其他線程看到的是完全構(gòu)造好的對(duì)象。
- 雙重檢查鎖定 (DCL) 的單例模式 在單例模式中,volatile 用于防止指令重排序?qū)е缕渌€程拿到一個(gè)未完全初始化的對(duì)象。
public class Singleton {
// volatile 防止 instance = new Singleton() 指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 可能發(fā)生重排序
}
}
}
return instance;
}
}3 總結(jié)
3.1 synchronized 與 volatile 的核心區(qū)別
| 特性 | synchronized | volatile |
|---|---|---|
| 作用對(duì)象 | 方法、代碼塊 | 變量 |
| 核心機(jī)制 | 互斥鎖 (Monitor) | 內(nèi)存屏障 (Memory Barrier) |
| 原子性 | 保證 (通過(guò)互斥實(shí)現(xiàn)) | 不保證 (僅保證單次讀/寫原子) |
| 可見性 | 保證 (進(jìn)出同步塊時(shí)刷新主內(nèi)存) | 保證 (強(qiáng)制讀寫主內(nèi)存) |
| 有序性 | 保證 (通過(guò)互斥和禁止重排序) | 保證 (通過(guò)內(nèi)存屏障禁止重排序) |
| 線程阻塞 | 會(huì)阻塞 (未獲取鎖的線程進(jìn)入阻塞狀態(tài)) | 不會(huì)阻塞 (線程可以繼續(xù)執(zhí)行) |
| 性能開銷 | 較大 (涉及操作系統(tǒng),可能上下文切換) | 較小 (主要是內(nèi)存屏障開銷) |
| 適用場(chǎng)景 | 復(fù)雜的原子操作、臨界區(qū)保護(hù) 簡(jiǎn)單的狀態(tài)標(biāo)志、 | 一次性安全發(fā)布、DCL單例模式 |
3.2 適用場(chǎng)景
3.2.1 狀態(tài)標(biāo)志控制 使用volatile
僅需保證可見性進(jìn)需要操作是原子的 (如 flag = true): 優(yōu)先使用 volatile,因?yàn)樗p量。
class TaskRunner {
private volatile boolean stopped = false; // 線程安全的狀態(tài)標(biāo)志
public void run() {
while (!stopped) { /* 執(zhí)行任務(wù) */ }
}
public void stop() { stopped = true; } // 修改立即可見
}3.2.2 單例模式(雙重檢查鎖定)synchronized+volatile
volatile防止new Singleton()的分解步驟重排序,避免返回未初始化的對(duì)象
class Singleton {
private static volatile Singleton instance; // 禁止指令重排序
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 禁止重排序:分配內(nèi)存→初始化→賦值引用
}
}
}
return instance;
}
}3.2.3 臨界區(qū)保護(hù) 使用synchronized
強(qiáng)制原子性,適合需要互斥訪問的復(fù)合操作(如讀寫共享變量)。
class BankAccount {
private double balance;
public synchronized void deposit(double amount) { // 整個(gè)方法同步
balance += amount;
}
public void withdraw(double amount) {
synchronized (this) { // 代碼塊同步
balance -= amount;
}
}
}3.2.4 線程協(xié)作(等待/通知機(jī)制)
synchronized提供鎖的獲取/釋放機(jī)制,配合wait()/notifyAll()實(shí)現(xiàn)線程間協(xié)作。
class ProducerConsumer {
private final Object lock = new Object();
private boolean isProduced = false;
public void produce() {
synchronized (lock) {
while (isProduced) { lock.wait(); } // 等待消費(fèi)
// 生產(chǎn)數(shù)據(jù)...
isProduced = true;
lock.notifyAll(); // 通知消費(fèi)者
}
}
public void consume() {
synchronized (lock) {
while (!isProduced) { lock.wait(); } // 等待生產(chǎn)
// 消費(fèi)數(shù)據(jù)...
isProduced = false;
lock.notifyAll(); // 通知生產(chǎn)者
}
}
}到此這篇關(guān)于Java中 synchronized 和 volatile的核心區(qū)別解析的文章就介紹到這了,更多相關(guān)Java synchronized 和 volatile內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java線程中synchronized和volatile關(guān)鍵字的區(qū)別詳解
- java多線程編程必備volatile與synchronized深入理解
- java Volatile與Synchronized的區(qū)別
- 詳解java并發(fā)編程(2) --Synchronized與Volatile區(qū)別
- Java線程之線程同步synchronized和volatile詳解
- java中volatile和synchronized的區(qū)別與聯(lián)系
- Java關(guān)鍵字volatile和synchronized作用和區(qū)別
- java多線程中的volatile和synchronized用法分析
相關(guān)文章
Spring Boot 中的 @PutMapping 注解原理及使用小結(jié)
在本文中,我們介紹了 Spring Boot 中的 @PutMapping 注解,它可以將 HTTP PUT 請(qǐng)求映射到指定的處理方法上,我們還介紹了 @PutMapping 注解的原理以及如何在 Spring Boot 中使用它,感興趣的朋友跟隨小編一起看看吧2023-12-12
Java泛型與數(shù)據(jù)庫(kù)應(yīng)用實(shí)例詳解
這篇文章主要介紹了Java泛型與數(shù)據(jù)庫(kù)應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了java繼承泛型類實(shí)現(xiàn)增刪改查操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-08-08
Java ThreadLocal的使用場(chǎng)景總結(jié)
ThreadLocal原本設(shè)計(jì)是為了解決并發(fā)時(shí),線程共享變量的問題,但由于過(guò)度設(shè)計(jì),從而導(dǎo)致它的理解難度大和使用成本高等問題。即便如此,ThreadLocal依舊有適合自己的使用場(chǎng)景,比如本文要介紹了這兩種使用場(chǎng)景,除了ThreadLocal之外,還真沒有合適的替代方案。2021-05-05
如何基于FTP4J實(shí)現(xiàn)FTPS連接過(guò)程解析
這篇文章主要介紹了如何基于FTP4J實(shí)現(xiàn)FTPS連接過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot使用?Sleuth?進(jìn)行分布式跟蹤的過(guò)程分析
Spring Boot Sleuth是一個(gè)分布式跟蹤解決方案,它可以幫助您在分布式系統(tǒng)中跟蹤請(qǐng)求并分析性能問題,Spring Boot Sleuth是Spring Cloud的一部分,它提供了分布式跟蹤的功能,本文將介紹如何在Spring Boot應(yīng)用程序中使用Sleuth進(jìn)行分布式跟蹤,感興趣的朋友一起看看吧2023-10-10
Spring Boot 項(xiàng)目集成 Redisson 實(shí)現(xiàn)延遲隊(duì)列的詳細(xì)過(guò)程
本文介紹延遲隊(duì)列在訂單超時(shí)等場(chǎng)景的應(yīng)用及四種技術(shù)方案對(duì)比,推薦Redisson延遲隊(duì)列,提供項(xiàng)目結(jié)構(gòu)與測(cè)試源碼,對(duì)Spring Boot Redisson延遲隊(duì)列相關(guān)知識(shí)感興趣的朋友一起看看吧2025-06-06
Spring Boot 集成 Quartz 使用Cron 表達(dá)式實(shí)現(xiàn)定
本文介紹了如何在SpringBoot項(xiàng)目中集成Quartz并使用Cron表達(dá)式進(jìn)行任務(wù)調(diào)度,通過(guò)添加Quartz依賴、創(chuàng)建Quartz任務(wù)、配置任務(wù)調(diào)度以及啟動(dòng)項(xiàng)目,可以實(shí)現(xiàn)定時(shí)任務(wù)的執(zhí)行,Cron表達(dá)式提供了靈活的任務(wù)調(diào)度方式,適用于各種復(fù)雜的定時(shí)任務(wù)需求,感興趣的朋友一起看看吧2025-03-03

