Java中的synchronized關(guān)鍵字實現(xiàn)原理及使用方式
在多線程編程中,保證線程安全是至關(guān)重要的。Java提供了synchronized關(guān)鍵字作為一種簡單而有效的同步機制,用于控制多個線程對共享資源的訪問。本文將詳細解析synchronized的實現(xiàn)原理、使用方式以及優(yōu)化策略。
1. synchronized的基本概念
1.1 什么是synchronized
synchronized是Java中的關(guān)鍵字,用于實現(xiàn)線程同步,確保多個線程在訪問共享資源時的互斥性。它可以保證在同一時刻,只有一個線程可以執(zhí)行某個方法或代碼塊,從而避免數(shù)據(jù)不一致的問題。
1.2 為什么需要synchronized
在多線程環(huán)境下,如果多個線程同時訪問和修改共享數(shù)據(jù),可能會導(dǎo)致數(shù)據(jù)不一致的問題。例如:
public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作
}
public int getCount() {
return count;
}
}count++看似是一個操作,但實際上包含三個步驟:
- 讀取count的當前值
- 將值加1
- 將新值寫回count
如果沒有同步機制,多個線程可能同時執(zhí)行這些步驟,導(dǎo)致結(jié)果不符合預(yù)期。
2. synchronized的三種使用方式
2.1 同步實例方法
public synchronized void increment() {
count++;
}
當synchronized修飾實例方法時,鎖對象是當前實例(this)。同一時刻,同一個實例的同步實例方法只能被一個線程訪問。
2.2 同步靜態(tài)方法
public static synchronized void staticIncrement() {
staticCount++;
}
當synchronized修飾靜態(tài)方法時,鎖對象是當前類的Class對象。同一時刻,該類的所有同步靜態(tài)方法只能被一個線程訪問。
2.3 同步代碼塊
public void increment() {
synchronized(this) {
count++;
}
}
// 或者使用其他對象作為鎖
private final Object lock = new Object();
public void incrementWithLock() {
synchronized(lock) {
count++;
}
}同步代碼塊可以更精細地控制同步范圍,減少鎖的粒度,提高性能。
3. synchronized的實現(xiàn)原理
3.1 對象頭與Monitor
在JVM中,每個對象都包含一個對象頭,其中存儲了與鎖相關(guān)的信息。對象頭主要包括:
- Mark Word:存儲對象的哈希碼、分代年齡、鎖標志位等
- Klass Pointer:指向?qū)ο笏鶎兕惖脑獢?shù)據(jù)
當使用synchronized時,JVM會根據(jù)鎖標志位來判斷鎖的狀態(tài)。
3.2 Monitor機制
synchronized的實現(xiàn)依賴于Monitor(監(jiān)視器)機制。每個Java對象都與一個Monitor相關(guān)聯(lián),Monitor是線程同步的基本工具。
Monitor的基本結(jié)構(gòu)包含:
- Owner:持有該Monitor的線程
- EntryList:處于阻塞狀態(tài)的線程隊列
- WaitSet:處于等待狀態(tài)的線程隊列
3.3 鎖的升級過程
為了平衡性能與安全性,Java中的鎖有幾種狀態(tài),并且可以根據(jù)競爭情況升級:
無鎖狀態(tài)
對象剛創(chuàng)建時,處于無鎖狀態(tài)。
偏向鎖
當?shù)谝粋€線程訪問同步塊時,會升級為偏向鎖。此時會在對象頭和棧幀中記錄偏向的線程ID。以后該線程進入同步塊時,不需要進行CAS操作來加鎖和解鎖,只需簡單測試一下對象頭的Mark Word是否指向當前線程。
輕量級鎖
當有第二個線程嘗試獲取鎖時,偏向鎖會升級為輕量級鎖。線程會通過CAS操作在棧幀中創(chuàng)建鎖記錄(Lock Record),并將對象頭的Mark Word復(fù)制到鎖記錄中,然后嘗試用CAS將對象頭的Mark Word替換為指向鎖記錄的指針。
重量級鎖
當輕量級鎖競爭激烈時(多個線程同時競爭),會升級為重量級鎖。此時未獲取到鎖的線程會進入阻塞狀態(tài),需要操作系統(tǒng)的介入來進行線程調(diào)度。
4. synchronized的底層實現(xiàn)
4.1 字節(jié)碼層面
從字節(jié)碼角度來看,synchronized同步代碼塊是通過monitorenter和monitorexit指令實現(xiàn)的:
public void method() {
synchronized(obj) {
// 同步代碼塊
}
}
對應(yīng)的字節(jié)碼:
0: aload_1 // 將obj引用壓入棧頂 1: dup // 復(fù)制棧頂值 2: astore_2 // 將復(fù)制的引用存儲到局部變量表 3: monitorenter // 進入監(jiān)視器 4: aload_2 // 將obj引用壓入棧頂 5: monitorexit // 退出監(jiān)視器(正常退出) 6: goto 14 9: astore_3 // 異常處理開始 10: aload_2 // 將obj引用壓入棧頂 11: monitorexit // 退出監(jiān)視器(異常退出) 12: aload_3 13: athrow 14: return // 方法返回
4.2 內(nèi)存語義
synchronized具有以下內(nèi)存語義:
- 進入同步塊:將工作內(nèi)存中的共享變量清空,從主內(nèi)存重新加載
- 退出同步塊:將工作內(nèi)存中的修改刷新到主內(nèi)存
這保證了可見性和有序性,遵循h(huán)appens-before原則。
5. synchronized的特性
5.1 原子性
synchronized保證了被它修飾的代碼塊或方法是原子操作的。一次只有一個線程可以執(zhí)行同步代碼,不會被打斷。
5.2 可見性
當線程釋放鎖時,JMM會將工作內(nèi)存中的共享變量刷新到主內(nèi)存中。當線程獲取鎖時,JMM會使該線程的工作內(nèi)存無效,從主內(nèi)存重新加載共享變量。
5.3 有序性
synchronized通過"一個變量在同一時刻只允許一條線程對其進行l(wèi)ock操作"來保證有序性。同時,由于as-if-serial語義,同步代碼塊內(nèi)的指令可以重排序,但不會影響執(zhí)行結(jié)果。
5.4 可重入性
synchronized是可重入鎖,同一個線程可以多次獲取同一把鎖:
public class ReentrantExample {
public synchronized void method1() {
method2(); // 可以再次獲取鎖
}
public synchronized void method2() {
// 方法實現(xiàn)
}
}6. synchronized的優(yōu)化策略
6.1 自旋鎖與自適應(yīng)自旋
為了避免線程在獲取鎖失敗時直接進入阻塞狀態(tài)(涉及操作系統(tǒng)內(nèi)核態(tài)切換,開銷大),JVM引入了自旋鎖。
自旋鎖的原理是:當線程獲取鎖失敗時,不立即阻塞,而是執(zhí)行一個空循環(huán)(自旋),在一定次數(shù)內(nèi)嘗試重新獲取鎖。如果自旋期間鎖被釋放,則可以避免線程阻塞。
自適應(yīng)自旋是自旋鎖的優(yōu)化,自旋時間不再固定,而是根據(jù)前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態(tài)來決定。
6.2 鎖消除
JVM在JIT編譯時,通過逃逸分析技術(shù),判斷同步塊使用的鎖對象是否只被一個線程訪問。如果是,則會消除這個同步操作。
public String concatString(String s1, String s2, String s3) {
return s1 + s2 + s3;
}在JDK 5之后,字符串連接會自動轉(zhuǎn)換為StringBuilder操作,而StringBuilder的append方法是同步的。但由于局部變量StringBuilder不會逃逸出方法,JVM會消除這些同步操作。
6.3 鎖粗化
如果一系列的連續(xù)操作都對同一個對象反復(fù)加鎖和解鎖,JVM會將鎖的同步范圍擴展(粗化)到整個操作序列的外部,減少不必要的鎖獲取和釋放。
public void method() {
for(int i = 0; i < 100; i++) {
synchronized(this) {
// 操作
}
}
}
JVM可能會將鎖粗化為:
public void method() {
synchronized(this) {
for(int i = 0; i < 100; i++) {
// 操作
}
}
}
7. synchronized的注意事項
7.1 性能考慮
雖然現(xiàn)代JVM對synchronized做了大量優(yōu)化,但在高并發(fā)場景下,它仍然可能成為性能瓶頸。需要考慮:
- 減少同步代碼塊的執(zhí)行時間
- 降低鎖的粒度(使用更細粒度的鎖)
- 考慮使用并發(fā)容器或其他并發(fā)工具
7.2 死鎖問題
synchronized可能導(dǎo)致死鎖,即兩個或多個線程互相等待對方釋放鎖:
// 線程1
synchronized(objA) {
synchronized(objB) {
// 操作
}
}
// 線程2
synchronized(objB) {
synchronized(objA) {
// 操作
}
}避免死鎖的策略:
- 按固定順序獲取鎖
- 使用定時鎖(tryLock)
- 避免嵌套鎖
7.3 與Lock的比較
與java.util.concurrent.locks.Lock相比,synchronized有以下特點:
| 特性 | synchronized | Lock |
|---|---|---|
| 實現(xiàn)機制 | JVM層面實現(xiàn) | Java代碼實現(xiàn) |
| 鎖的獲取 | 自動獲取和釋放 | 手動控制 |
| 可中斷性 | 不支持 | 支持 |
| 公平性 | 非公平 | 可選擇公平或非公平 |
| 條件變量 | 通過wait/notify | 支持多個Condition |
8. 實際應(yīng)用示例
8.1 線程安全的單例模式
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}8.2 生產(chǎn)者-消費者模式
public class Buffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
public Buffer(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(int value) throws InterruptedException {
while (queue.size() == capacity) {
wait();
}
queue.add(value);
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
int value = queue.poll();
notifyAll();
return value;
}
}9. 總結(jié)
synchronized是Java中實現(xiàn)線程同步的基本工具,它提供了簡單而有效的互斥機制。隨著JVM的發(fā)展,synchronized的性能已經(jīng)得到了顯著提升,在許多場景下可以滿足性能要求。
然而,在高并發(fā)場景下,開發(fā)人員仍需謹慎使用synchronized,考慮鎖的粒度、競爭情況以及可能的替代方案。理解synchronized的實現(xiàn)原理和優(yōu)化策略,有助于編寫出更高效、更安全的并發(fā)程序。
在實際開發(fā)中,應(yīng)根據(jù)具體需求選擇合適的同步機制,平衡性能、復(fù)雜性和可維護性。
到此這篇關(guān)于Java中的synchronized關(guān)鍵字實現(xiàn)原理及使用方式的文章就介紹到這了,更多相關(guān)java synchronized關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中復(fù)雜的Synchronized關(guān)鍵字使用方法詳解
- Java關(guān)鍵字synchronized基本使用詳解
- Java?synchronized關(guān)鍵字性能考量及優(yōu)化探索
- 詳解Java synchronized關(guān)鍵字的用法
- 深入了解Java中Synchronized關(guān)鍵字的實現(xiàn)原理
- 簡單了解Java synchronized關(guān)鍵字同步
- Java中的關(guān)鍵字synchronized 詳解
- Java多線程并發(fā)編程 Synchronized關(guān)鍵字
- Java中synchronized關(guān)鍵字修飾方法同步的用法詳解
相關(guān)文章
java的Jackson將json字符串轉(zhuǎn)換成泛型List
這篇文章主要介紹了java的Jackson將json字符串轉(zhuǎn)換成泛型List ,這里整理了詳細的代碼,有需要的小伙伴可以參考下。2017-02-02
springboot2.7報錯:request header is too la
本文詳細介紹了在Spring Boot 2.7中使用@RequestParam傳參時,請求頭大小超過限制導(dǎo)致報錯的解決方案,包括調(diào)整配置文件、通過代碼配置和直接配置Tomcat等方法,并提醒注意事項2026-01-01
springboot?vue測試平臺接口定義前后端新增功能實現(xiàn)
這篇文章主要介紹了springboot?vue測試平臺接口定義前后端新增功能實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05
Java利用線程工廠監(jiān)控線程池的實現(xiàn)示例
這篇文章主要介紹了Java利用線程工廠監(jiān)控線程池的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
通過簡單方法實現(xiàn)spring boot web項目
這篇文章主要介紹了通過簡單方法實現(xiàn)spring boot web項目,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09
IntelliJ IDEA 小技巧之Bookmark(書簽)的使用
這篇文章主要介紹了IntelliJ IDEA 小技巧之Bookmark(書簽)的使用,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
Spring MVC請求參數(shù)與響應(yīng)結(jié)果全局加密和解密詳解
這篇文章主要給大家介紹了關(guān)于Spring MVC請求參數(shù)與響應(yīng)結(jié)果全局加密和解密的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08

