Java鎖面試題詳解

一、鎖的概念與種類

鎖通常用於保證多線程訪問共享資源的安全性,它能有效地避免資源競爭,確保多線程之間的數據同步。Java中的鎖可以分為以下幾種:

1. synchronized鎖

public synchronized void method() {
    // critical section
}

使用synchronized關鍵字修飾的方法或代碼塊,即可將其變成同步方法(或同步代碼塊)。它是Java中最基本的鎖實現方式。

2. ReentrantLock鎖

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

ReentrantLock是一個可重入鎖,支持公平性和非公平性操作,並提供了比synchronized更加靈活的鎖控制。

3. ReadWriteLock鎖

ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
try {
    // read
} finally {
    lock.readLock().unlock();
}
lock.writeLock().lock();
try {
    // write
} finally {
    lock.writeLock().unlock();
}

ReadWriteLock允許多個線程同時讀取共享資源,但只能由一個線程修改資源。因此,它的讀寫鎖分別有readLock和writeLock兩種。

二、死鎖與活鎖

1. 死鎖

死鎖是指多個線程在獲取鎖的過程中,由於互相等待對方釋放鎖而陷入阻塞的狀態,無法繼續執行下去。如下代碼就存在死鎖的風險:

public class Deadlock {
    private static final Object obj1 = new Object();
    private static final Object obj2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (obj1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("Thread1 finished");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (obj2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("Thread2 finished");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

以上代碼中,t1和t2先後獲取到obj1和obj2,但又同時等待對方釋放鎖,從而導致死鎖的產生。

2. 活鎖

活鎖是指多個線程在獲取鎖的過程中,由於過度地釋放鎖或者過度地交換執行權,導致線程一直在重複執行同一段代碼,無法繼續向前執行。

三、鎖的優化

1. 鎖的粗化

鎖的粗化是指將一些連續的同步操作拼接在一起,減少線程競爭的次數。比如以下代碼:

public synchronized void method() {
    // step1
    // step2
    // step3
    // step4
}

以上代碼中,step1 ~ step4是一系列的同步操作,如果不進行粗化處理,則在每個步驟之間都需要獲取和釋放鎖,造成較大的時間和性能消耗。可以將代碼粗化,將所有同步操作都放在一個同步塊中:

public void method() {
    synchronized (this) {
        // step1
        // step2
        // step3
        // step4
    }
}

2. 鎖的細化

鎖的細化是指將原本的鎖拆解成多個鎖,從而減少競爭的粒度。比如以下代碼:

public synchronized void method() {
    // step1
    // step2
    // step3
    // step4
}

以上代碼中,如果step1 ~ step4之間存在比較重的計算操作,會導致該方法在執行時無法同時處理其它的請求。可以將鎖進行細化,將步驟拆解出來,從而減少競爭的粒度:

private final Object lock1 = new Object();
private final Object lock2 = new Object();
private final Object lock3 = new Object();
private final Object lock4 = new Object();

public void method() {
    synchronized (lock1) {
        // step1
    }
    synchronized (lock2) {
        // step2
    }
    synchronized (lock3) {
        // step3
    }
    synchronized (lock4) {
        // step4
    }
}

3. 樂觀鎖

樂觀鎖是一種無鎖的實現方式,它假設並發的衝突非常少,並通過CAS操作來檢查和更新數據。對於不頻繁的衝突,樂觀鎖可以在多線程並發中提供良好的性能和吞吐量。Java中的Atomic類就是樂觀鎖的一種實現方式。

四、鎖的使用場景

1. 高並發訪問的共享資源

在高並發訪問下,共享資源容易被多個線程同時訪問,導致數據不一致等問題。這時可以使用鎖來保證數據的一致性。例如收銀台模擬程序中,多個售票員同時向一個賬戶扣款,需要使用鎖保證操作的原子性。

2. 多線程間的通信

在多線程間的通信中,常常需要一個線程等待另一個線程執行完畢,在該線程繼續執行。這時可以使用鎖來實現線程之間的互斥和同步。例如生產者和消費者模型中,需要用鎖機制來實現對共享資源(緩衝區)的同步訪問。

3. 避免數據競爭

數據競爭是指當兩個或多個線程同時訪問某個變量,並且至少其中一個線程對變量進行了寫操作,此時可能會導致變量的值不確定性,出現嚴重錯誤。這時可以使用鎖來避免數據競爭的產生。

五、案例演示

以下是一個使用ReentrantLock鎖的示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public int incrementAndGet() {
        lock.lock();
        try {
            Thread.sleep(100); // 模擬長時間計算
            return ++count;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Runnable runnable = () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + counter.incrementAndGet());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("count: " + counter.count);
    }
}

運行以上代碼,可以看到輸出結果:

Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-1: 4
Thread-1: 5
Thread-1: 6
count: 6

以上代碼中,counter是一個計數器,在incrementAndGet方法中使用了ReentrantLock鎖,來保證count變量的線程安全。在main方法中,開啟兩個線程同時訪問incrementAndGet方法,從而驗證鎖的效果。

原創文章,作者:DXURW,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/333402.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
DXURW的頭像DXURW
上一篇 2025-02-01 13:34
下一篇 2025-02-01 13:34

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Bean加載過程

    Java Bean加載過程涉及到類加載器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean加載的過程。 一、類加載器 類加載器是Java虛擬機…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java語言中的一個版本,於2014年3月18日發佈。本文將從多個方面對Java 8中某一周的周一進行詳細的闡述。 一、數組處理 Java 8新特性之一是Stream…

    編程 2025-04-29
  • Java判斷字符串是否存在多個

    本文將從以下幾個方面詳細闡述如何使用Java判斷一個字符串中是否存在多個指定字符: 一、字符串遍歷 字符串是Java編程中非常重要的一種數據類型。要判斷字符串中是否存在多個指定字符…

    編程 2025-04-29
  • VSCode為什麼無法運行Java

    解答:VSCode無法運行Java是因為默認情況下,VSCode並沒有集成Java運行環境,需要手動添加Java運行環境或安裝相關插件才能實現Java代碼的編寫、調試和運行。 一、…

    編程 2025-04-29
  • Java任務下發回滾系統的設計與實現

    本文將介紹一個Java任務下發回滾系統的設計與實現。該系統可以用於執行複雜的任務,包括可回滾的任務,及時恢復任務失敗前的狀態。系統使用Java語言進行開發,可以支持多種類型的任務。…

    編程 2025-04-29
  • Java 8 Group By 會影響排序嗎?

    是的,Java 8中的Group By會對排序產生影響。本文將從多個方面探討Group By對排序的影響。 一、Group By的概述 Group By是SQL中的一種常見操作,它…

    編程 2025-04-29

發表回復

登錄後才能評論