最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 同步關(guān)鍵字 synchronized用法 、場景及避坑指南

 更新時間:2025年12月22日 12:02:27   作者:heartbeat..  
synchronized 是 Java 最基礎、最常用的線程同步方式,優(yōu)點是 簡單易用、無需手動釋放鎖、JVM 自動優(yōu)化,適合解決大多數(shù)線程安全問題,本文給大家介紹Java 同步關(guān)鍵字 synchronized用法 + 場景 + 避坑指南,感興趣的朋友跟隨小編一起看看吧

Java 同步關(guān)鍵字 synchronized:用法 + 場景 + 避坑指南

一、介紹

在 Java 中,synchronized內(nèi)置的線程同步機制,用于解決多線程并發(fā)訪問共享資源時的線程安全問題(如競態(tài)條件、數(shù)據(jù)不一致)。其核心原理是 通過 “鎖” 控制多個線程對共享資源的互斥訪問,確保同一時刻只有一個線程能執(zhí)行臨界區(qū)代碼。

二、主要特點

  1. 互斥性:同一時刻,只有一個線程能持有鎖并執(zhí)行臨界區(qū)代碼(其他線程需阻塞等待鎖釋放)。
  2. 可見性:鎖釋放時,線程對共享變量的修改會被 “刷新” 到主內(nèi)存;其他線程獲取鎖時,會從主內(nèi)存重新讀取變量(避免緩存一致性問題)。
  3. 有序性:禁止指令重排序(臨界區(qū)代碼在多線程視角下按順序執(zhí)行)。

舉個例子:

synchronized 就像一個 “共享資源的專屬門衛(wèi)”

  • 它守著一個 “資源入口”(臨界區(qū));
  • 每個想用資源的 “線程”,都得先跟門衛(wèi)要 “通行證”(獲取鎖);
  • 門衛(wèi)只給一個人發(fā)通行證,其他人只能在門口排隊(阻塞);
  • 拿到通行證的人用完資源后,必須把通行證還給門衛(wèi)(釋放鎖),下一個排隊的人才能拿到;
  • 門衛(wèi)還認人(可重入性):同一個人再要通行證,直接給,不用排隊;
  • 門衛(wèi)還能靈活守 “小門”(代碼塊)或 “大門”(整個方法):守小門效率高,守大門簡單但容易堵。

synchronized 的核心作用 ——讓多線程 “排隊使用共享資源”,避免混亂和沖突。

三、使用場景(3 種形式)

synchronized 可修飾 方法代碼塊,本質(zhì)是對 “鎖對象” 加鎖,鎖的粒度決定同步范圍。

1. 修飾實例方法(對象鎖)

  • 鎖對象:當前 實例對象(this)。
  • 效果:同一實例的多個線程,競爭同一把鎖;不同實例的線程互不影響(各自持有自己的鎖)。
public class SynchronizedDemo {
    // 實例方法加鎖:鎖是當前對象(this)
    public synchronized void instanceMethod() {
        // 臨界區(qū):共享資源操作(如修改實例變量)
        System.out.println(Thread.currentThread().getName() + " 執(zhí)行實例方法");
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        // 兩個線程競爭 demo 實例的鎖,串行執(zhí)行
        new Thread(demo::instanceMethod, "線程1").start();
        new Thread(demo::instanceMethod, "線程2").start(); // 等待線程1釋放鎖
    }
}

2. 修飾靜態(tài)方法(類鎖)

  • 鎖對象:當前類的 Class 對象(類的唯一全局鎖)。
  • 效果:所有該類的實例(無論多少個對象)共享同一把鎖,多線程競爭類鎖時串行執(zhí)行。
public class SynchronizedDemo {
    // 靜態(tài)方法加鎖:鎖是 SynchronizedDemo.class
    public static synchronized void staticMethod() {
        System.out.println(Thread.currentThread().getName() + " 執(zhí)行靜態(tài)方法");
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
    public static void main(String[] args) {
        SynchronizedDemo demo1 = new SynchronizedDemo();
        SynchronizedDemo demo2 = new SynchronizedDemo();
        // 兩個線程競爭同一把類鎖(SynchronizedDemo.class),串行執(zhí)行
        new Thread(demo1::staticMethod, "線程A").start();
        new Thread(demo2::staticMethod, "線程B").start(); // 等待線程A釋放鎖
    }
}

3. 修飾代碼塊(顯式指定鎖對象)

  • 鎖對象:可自定義(任意 非 null 對象,如 this、Class 對象、自定義對象)。
  • 效果:僅對代碼塊內(nèi)的邏輯加鎖,粒度更細(推薦,減少鎖競爭開銷)。
public class SynchronizedDemo {
    private final Object lock = new Object(); // 自定義鎖對象(推薦用 final,避免鎖對象被修改)
    private int count = 0;
    public void syncBlock() {
        // 1. 自定義對象鎖:鎖定 lock 對象
        synchronized (lock) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":count=" + count);
        }
        // 2. 實例鎖(等價于修飾實例方法)
        synchronized (this) {
            // 臨界區(qū)邏輯
        }
        // 3. 類鎖(等價于修飾靜態(tài)方法)
        synchronized (SynchronizedDemo.class) {
            // 臨界區(qū)邏輯
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        // 多線程競爭 lock 對象,count 自增線程安全
        for (int i = 0; i < 3; i++) {
            new Thread(demo::syncBlock, "線程" + i).start();
        }
    }
}

舉個示例:

假設某電商平臺有一款爆款商品,庫存僅剩 10 件。同時有 100 個用戶搶購(每個用戶對應一個線程),每個用戶搶購 1 件商品。要求:

  1. 庫存不能為負數(shù);
  2. 最終賣出的商品數(shù)量 = 初始庫存(不能多賣,也不能少賣)。

如果不加 synchronized,會出現(xiàn) “超賣”(比如庫存 10,但賣出 12 件);加了 synchronized 后,能保證線程安全,精準扣減庫存。

不加synchronized(線程不安全,會超賣)

public class StockDemo {
    // 商品初始庫存:10件
    private int stock = 10;
    // 搶購方法(未加鎖)
    public void buy() {
        // 1. 檢查庫存是否充足
        if (stock > 0) {
            // 模擬網(wǎng)絡延遲(放大線程安全問題)
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            // 2. 庫存扣減
            stock--;
            // 3. 打印搶購結(jié)果
            System.out.println(Thread.currentThread().getName() + " 搶購成功!剩余庫存:" + stock);
        } else {
            System.out.println(Thread.currentThread().getName() + " 搶購失敗!庫存不足");
        }
    }
    public static void main(String[] args) {
        StockDemo stock = new StockDemo();
        // 100個用戶同時搶購(100個線程)
        for (int i = 0; i < 100; i++) {
            new Thread(stock::buy, "用戶" + (i + 1)).start();
        }
    }
}

結(jié)果:

用戶1 搶購成功!剩余庫存:9
用戶2 搶購成功!剩余庫存:8
...
用戶10 搶購成功!剩余庫存:0
用戶11 搶購成功!剩余庫存:-1  // 超賣了!庫存為負數(shù)
用戶12 搶購成功!剩余庫存:-2  // 繼續(xù)超賣

問題原因:多個線程同時進入 if (stock > 0) 判斷(比如庫存還剩 1 時,5 個線程同時通過判斷),之后都執(zhí)行 stock--,導致庫存被多次扣減,出現(xiàn)負數(shù)。

加synchronized(線程安全,無超賣)

buy() 方法加 synchronized,或給 “庫存檢查 + 扣減” 的臨界區(qū)加鎖,保證同一時刻只有一個線程能執(zhí)行核心邏輯:

public class StockDemo {
    private int stock = 10;
    // 方案1:修飾實例方法(鎖對象是當前 StockDemo 實例)
    public synchronized void buy() {
        if (stock > 0) {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            stock--;
            System.out.println(Thread.currentThread().getName() + " 搶購成功!剩余庫存:" + stock);
        } else {
            System.out.println(Thread.currentThread().getName() + " 搶購失??!庫存不足");
        }
    }
    // 方案2:修飾代碼塊(鎖對象自定義,粒度更細,推薦)
    /*
    public void buy() {
        synchronized (this) {  // 鎖當前實例,也可以用自定義鎖對象(如 private final Object lock = new Object();)
            if (stock > 0) {
                try { Thread.sleep(10); } catch (InterruptedException e) {}
                stock--;
                System.out.println(Thread.currentThread().getName() + " 搶購成功!剩余庫存:" + stock);
            } else {
                System.out.println(Thread.currentThread().getName() + " 搶購失??!庫存不足");
            }
        }
    }
    */
    public static void main(String[] args) {
        StockDemo stock = new StockDemo();
        // 100個用戶同時搶購
        for (int i = 0; i < 100; i++) {
            new Thread(stock::buy, "用戶" + (i + 1)).start();
        }
    }
}

結(jié)果:

用戶1 搶購成功!剩余庫存:9
用戶2 搶購成功!剩余庫存:8
...
用戶10 搶購成功!剩余庫存:0
用戶11 搶購失敗!庫存不足
用戶12 搶購失?。齑娌蛔?br />...
用戶100 搶購失?。齑娌蛔?/p>

擴展:如果是多實例場景?

如果電商平臺部署了多個服務實例(每個實例都是一個 StockDemo 對象),此時 synchronized(對象鎖 / 類鎖)只能保證 “單個實例內(nèi)的線程安全”,無法跨實例同步(比如實例 1 的庫存 10,實例 2 的庫存 10,可能導致總超賣 20 件)。

這種情況需要 分布式鎖(如 Redis 分布式鎖、ZooKeeper 分布式鎖),本質(zhì)是把 “鎖” 放到多個實例都能訪問的公共地方(如 Redis),實現(xiàn)跨實例的互斥。

但單實例內(nèi)的并發(fā)安全,synchronized 完全能搞定,且簡單高效。

四、鎖的實現(xiàn)原理(JVM 層面)

synchronized 早期基于 重量級鎖(依賴操作系統(tǒng)內(nèi)核的互斥量 mutex,切換成本高),JDK 6 后引入 鎖優(yōu)化(偏向鎖、輕量級鎖、重量級鎖),通過 對象頭(Mark Word) 存儲鎖狀態(tài):

1. 對象頭(Mark Word)結(jié)構(gòu)

Java 對象在內(nèi)存中分為 3 部分:對象頭、實例數(shù)據(jù)、對齊填充。其中 Mark Word 是實現(xiàn)鎖的核心,存儲內(nèi)容隨鎖狀態(tài)變化:

鎖狀態(tài)Mark Word 存儲內(nèi)容
無鎖對象哈希碼、GC 分代年齡、是否偏向鎖(0)
偏向鎖偏向線程 ID、GC 分代年齡、是否偏向鎖(1)
輕量級鎖指向線程棧中鎖記錄(Lock Record)的指針
重量級鎖指向 JVM 中監(jiān)視器鎖(Monitor)的指針

2. 鎖升級流程(從低開銷到高開銷)

JVM 按 “競爭程度” 動態(tài)升級鎖,避免不必要的性能損耗:

  1. 偏向鎖:單線程場景下,線程獲取鎖后,Mark Word 記錄線程 ID,后續(xù)無需再次競爭(直接復用鎖),開銷極低。
  2. 輕量級鎖:多線程交替執(zhí)行臨界區(qū)(無激烈競爭),線程通過 CAS(原子操作) 將 Mark Word 替換為自己的鎖記錄指針,避免內(nèi)核態(tài)切換。
  3. 重量級鎖:多線程同時競爭鎖(激烈競爭),CAS 失敗的線程阻塞等待(依賴操作系統(tǒng) mutex),開銷最高。

五、關(guān)鍵特性

  1. 可重入性:同一線程可多次獲取同一把鎖(不會死鎖)。例如:
public synchronized void methodA() {
    methodB(); // 同一線程再次獲取當前對象鎖,允許執(zhí)行
}
public synchronized void methodB() {}
  1. 原理:鎖記錄中維護 重入計數(shù)器,線程首次獲取鎖時計數(shù)器 = 1,再次獲取時計數(shù)器 + 1,釋放時計數(shù)器 - 1,直至為 0 釋放鎖。
  2. 不可中斷性:線程獲取鎖時(如重量級鎖),若鎖被占用,線程會進入 BLOCKED 狀態(tài),無法被中斷(需等待鎖釋放)。
  3. 非公平鎖:默認是 非公平鎖(線程釋放鎖后,等待隊列中的線程和新請求鎖的線程競爭,新線程可能 “插隊”),無法直接改為公平鎖(需借助 ReentrantLock)。

六、與 Lock 接口的對比

synchronized 是 JVM 內(nèi)置鎖,java.util.concurrent.locks.Lock 是 API 層面的鎖,核心差異如下:

特性synchronizedLock(如 ReentrantLock)
鎖實現(xiàn)JVM 內(nèi)置(C++ 實現(xiàn))Java 代碼實現(xiàn)(API 層面)
鎖類型非公平鎖(默認)可公平 / 非公平(構(gòu)造函數(shù)指定)
可中斷性不可中斷(BLOCKED 狀態(tài))可中斷(lockInterruptibly()
超時獲取無(只能無限等待)支持超時獲?。?code>tryLock(long, TimeUnit))
條件變量隱式(通過 wait()/notify()顯式(Condition 對象,支持多條件)
鎖釋放自動釋放(方法退出 / 代碼塊結(jié)束)必須手動釋放(unlock(),需在 finally 中)
性能優(yōu)化后(偏向 / 輕量級鎖)接近 Lock高并發(fā)下更靈活(如非阻塞獲取)

七、使用注意事項

  • 鎖粒度不宜過大:避免對整個方法加鎖(尤其是包含非臨界區(qū)邏輯時),優(yōu)先使用代碼塊鎖定最小臨界區(qū)(減少鎖競爭)。
  • 鎖對象不可變:代碼塊鎖的對象需用 final 修飾(避免鎖對象被重新賦值,導致多線程持有不同鎖,失去同步效果)。
  • 避免死鎖
    • 多個線程獲取鎖的順序保持一致(如線程 1 先鎖 A 再鎖 B,線程 2 也先鎖 A 再鎖 B);
    • 避免鎖嵌套過深;
    • 可使用超時鎖(Lock.tryLock())替代 synchronized 防止死鎖。
  • 避免鎖競爭激烈場景
    • 高并發(fā)下,synchronized 重量級鎖的性能較差,可考慮用ConcurrentHashMap 等并發(fā)容器,或通過分段鎖、無鎖編程(CAS)優(yōu)化。

八、總結(jié)

synchronized 是 Java 最基礎、最常用的線程同步方式,優(yōu)點是 簡單易用、無需手動釋放鎖、JVM 自動優(yōu)化,適合解決大多數(shù)線程安全問題(如簡單的共享變量修改、方法同步)。其核心是通過 “鎖對象” 實現(xiàn)互斥訪問,結(jié)合 JDK 6 后的鎖升級機制,在低競爭場景下性能優(yōu)異,高競爭場景下可考慮用 Lock 接口或并發(fā)容器替代,若是多實例場景使用分布式鎖。

到此這篇關(guān)于Java 同步關(guān)鍵字 synchronized:用法 + 場景 + 避坑指南的文章就介紹到這了,更多相關(guān)Java 關(guān)鍵字 synchronized內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot詳解MySQL如何實現(xiàn)讀寫分離

    SpringBoot詳解MySQL如何實現(xiàn)讀寫分離

    當響應的瓶頸在數(shù)據(jù)庫的時候,就要考慮數(shù)據(jù)庫的讀寫分離,當然還可以分庫分表,那是單表數(shù)據(jù)量特別大,當單表數(shù)據(jù)量不是特別大,但是請求量比較大的時候,就要考慮讀寫分離了.具體的話,還是要看自己的業(yè)務...如果還是很慢,那就要分庫分表了...我們這篇就簡單講一下讀寫分離
    2022-09-09
  • Java高并發(fā)中的交換器Exchanger解析

    Java高并發(fā)中的交換器Exchanger解析

    這篇文章主要介紹了Java高并發(fā)中的交換器Exchanger解析,如果兩個線程并行處理,但在某個時刻需要互相交換自己已經(jīng)處理完的中間數(shù)據(jù),然后才能繼續(xù)往下執(zhí)行,這個時候就可以使用 Exchanger,需要的朋友可以參考下
    2023-12-12
  • JAVA多線程中join()方法的使用方法

    JAVA多線程中join()方法的使用方法

    雖然關(guān)于討論線程join()方法的博客已經(jīng)非常極其特別多了,但是前幾天我有一個困惑卻沒有能夠得到詳細解釋,當系統(tǒng)中正在運行多個線程時,join()到底是暫停了哪些線程,所以本文詳細解釋一下希望能幫助到和我有相同困惑的同學
    2021-05-05
  • 基于@Table注解無法使用及報紅的解決

    基于@Table注解無法使用及報紅的解決

    這篇文章主要介紹了基于@Table注解無法使用及報紅的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • mybatis批量插入,批量更新以及null值問題的解決

    mybatis批量插入,批量更新以及null值問題的解決

    這篇文章主要介紹了mybatis批量插入,批量更新以及null值問題的解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Mybatis XML文件中使用CDATA標簽的實現(xiàn)

    Mybatis XML文件中使用CDATA標簽的實現(xiàn)

    本文主要介紹了Mybatis XML文件中使用CDATA標簽,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-03-03
  • Java之注解@Data和@ToString(callSuper=true)解讀

    Java之注解@Data和@ToString(callSuper=true)解讀

    在使用Lombok庫的@Data注解時,若子類未通過@ToString(callSuper=true)注明包含父類屬性,toString()方法只打印子類屬性,解決方法:1. 子類重寫toString方法;2. 子類使用@Data和@ToString(callSuper=true),父類也應使用@Data
    2024-11-11
  • vue+springboot項目上傳部署tomcat的方法實現(xiàn)

    vue+springboot項目上傳部署tomcat的方法實現(xiàn)

    本文主要介紹了vue+springboot項目上傳部署tomcat的方法實現(xiàn),包括環(huán)境準備、配置調(diào)整以及部署步驟,文中通過圖文及示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-01-01
  • PowerJob的MapProcessor工作流程源碼解讀

    PowerJob的MapProcessor工作流程源碼解讀

    這篇文章主要為大家介紹了PowerJob的MapProcessor工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • Java打印數(shù)組的三種方法整理

    Java打印數(shù)組的三種方法整理

    許多學編程專業(yè)的同學面試的時候,考官都會問到Java如何打印數(shù)組這樣的問題,下面這篇文章主要給大家介紹了關(guān)于Java打印數(shù)組的三種方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-06-06

最新評論

长宁区| 合山市| 仪陇县| 额尔古纳市| 黄梅县| 徐汇区| 屏东县| 钦州市| 龙州县| 信丰县| 惠东县| 宁德市| 美姑县| 鸡西市| 大足县| 张北县| 金寨县| 长葛市| 永福县| 贵阳市| 西乡县| 宁河县| 丰原市| 彩票| 五寨县| 辉南县| 丹江口市| 定西市| 大港区| 徐汇区| 皮山县| 莫力| 乌鲁木齐市| 津南区| 翼城县| 萨嘎县| 屏东县| 双江| 板桥市| 水城县| 昌黎县|