首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程進階筆記 (二):同步代碼塊、同步方法、Lock 鎖、死鎖、線程安全類選型

     編輯:Java 相關知識

前言

多線程併發訪問共享資源會導致資料競爭。本文系統整理同步代碼塊、同步方法、Lock 介面、死鎖成因與避免、釋放鎖時機、線程安全類選型原則,配合完整可執行範例。

一、同步代碼塊

語法


synchronized(同步監視器對象) {
    // 同步代碼:同一時間僅一線程執行
}

同步監視器 (鎖對象)

  • 任意物件皆可:new Object()、this、ClassName.class
  • 多線程必須共用同一把鎖才能互斥
  • 執行完畢自動釋放鎖

範例:賣票問題修正


class TicketSeller implements Runnable {
    private int tickets = 500;
    private final Object lock = new Object(); // 專用鎖對象

    @Override
    public void run() {
        synchronized (lock) {
            for (; tickets > 0; tickets--) {
                System.out.println(Thread.currentThread() + " 賣出第 " + tickets + " 張票");
                try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                if (tickets == 1) System.out.println("票賣完,線程:" + Thread.currentThread());
            }
        }
    }
}

二、同步方法

語法


public synchronized void run() { ... } // 實例方法,鎖為 this
public static synchronized void staticMethod() { ... } // 靜態方法,鎖為 ClassName.class

隱式鎖

  • 實例方法:this
  • 靜態方法:當前類.class

範例:同步方法版賣票


class TicketSeller2 implements Runnable {
    private int tickets = 500;
    @Override
    public synchronized void run() { // 鎖 this
        for (; tickets > 0; tickets--) {
            System.out.println(Thread.currentThread() + " 賣出第 " + tickets + " 張票");
        }
    }
}

三、釋放鎖的時機

情況是否釋放鎖
同步代碼塊/方法正常結束✓ 釋放
break / return 結束✓ 釋放
拋出 Exception / Error✓ 釋放
同步監視器呼叫 wait()✓ 釋放 (進入等待集)
Thread.sleep() / yield()✗ 不釋放
其他線程呼叫 suspend()✗ 不釋放 (不建議使用)

四、Lock 介面與 ReentrantLock

優勢

  • 可中斷獲取鎖:lockInterruptibly()
  • 可設定超時:tryLock(timeout, unit)
  • 公平鎖選項:new ReentrantLock(true)
  • 條件變數:newCondition() 支援多組等待隊列
  • 可查詢鎖狀態:isLocked(), getHoldCount()

範例:ReentrantLock 版賣票


class TicketSeller3 implements Runnable {
    private final ReentrantLock lock = new ReentrantLock();
    private int tickets = 50;

    @Override
    public void run() {
        lock.lock(); // 加鎖
        try {
            for (; tickets > 0; tickets--) {
                System.out.println(Thread.currentThread() + " 賣出第 " + tickets + " 張票");
                if (tickets == 1) System.out.println("票賣完:" + Thread.currentThread());
            }
        } finally {
            lock.unlock(); // 必須在 finally 釋放
        }
    }
}

五、讀寫鎖 ReentrantReadWriteLock

  • ReadLock:共享鎖,讀讀並行,讀寫互斥
  • WriteLock:獨佔鎖,寫寫/寫讀互斥
  • 適用:讀多寫少場景 (緩存、配置)

六、死鎖

成因

線程 A 持有鎖 1 等待鎖 2,線程 B 持有鎖 2 等待鎖 1 → 循環等待

經典死鎖程式碼


class DeadLock implements Runnable {
    Object a = new Object();
    Object b = new Object();

    public void init() {
        new Thread(this).start(); // 子線程
        aMethod(b);               // 主線程
    }

    @Override
    public void run() {
        bMethod(a); // 子線程
    }

    public synchronized void aMethod(Object other) {
        System.out.println(Thread.currentThread().getName() + " 執行 aMethod");
        try { Thread.sleep(10); } catch (Exception e) {}
        System.out.println("準備呼叫 other.bMethod()");
        synchronized (other) { other.bMethod(); } // 需要 other 的鎖
    }

    public synchronized void bMethod() {
        System.out.println("bMethod 執行");
    }
}

避免死鎖原則

  1. 固定鎖獲取順序 (全域有序)
  2. 使用 tryLock(timeout) 超時放棄
  3. 減少鎖持有範圍 (細粒度鎖)
  4. 避免在同步塊中呼叫外部方法
  5. 使用並發容器替代手動同步

七、其他最佳實踐

  1. 同步粒度最小化:僅同步修改共享資源的核心代碼
  2. 不可變物件:String、Integer、自定義 final 類天然線程安全
  3. 線程封閉:ThreadLocal、棧封閉、Ad-hoc 封閉
  4. 雙版本提供:單線程用 StringBuilder、多線程用 StringBuffer (JDK 模式)
  5. 優先用並發容器:ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue

總結

  • synchronized:隱式鎖、語法簡單、JVM 優化好、適用大多數場景
  • Lock:顯式鎖、功能豐富 (可中斷、超時、公平、條件)、複雜場景首選
  • 死鎖核心:循環等待 → 破環順序/超時/縮小範圍
  • 釋放鎖時機決定併發度與安全性邊界
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved