欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳細(xì)盤點(diǎn)Java中鎖的分類

 更新時(shí)間:2023年08月08日 09:00:33   作者:索碼理  
這篇文章主要介紹了詳細(xì)盤點(diǎn)Java中鎖的分類,Java中的鎖是一種多線程編程中的同步機(jī)制,用于控制線程對(duì)共享資源的訪問,防止并發(fā)訪問時(shí)的數(shù)據(jù)競(jìng)爭(zhēng)和死鎖問題,需要的朋友可以參考下

什么是鎖

Java中的鎖是一種多線程編程中的同步機(jī)制,用于控制線程對(duì)共享資源的訪問,防止并發(fā)訪問時(shí)的數(shù)據(jù)競(jìng)爭(zhēng)和死鎖問題。

通過使用鎖機(jī)制,可以實(shí)現(xiàn)數(shù)據(jù)的同步訪問,確保多個(gè)線程安全地訪問共享資源,從而提高程序的并發(fā)性能。

隱式鎖和顯式鎖

顯式鎖和隱式鎖是根據(jù)鎖的獲取和釋放方式來進(jìn)行區(qū)分的。

隱式鎖

隱式鎖(Implicit Lock,又稱為內(nèi)置鎖或自動(dòng)鎖)是通過Java中的 synchronized 關(guān)鍵字來實(shí)現(xiàn)的,它在代碼塊或方法上加上 synchronized 關(guān)鍵字,從而隱式地獲取和釋放鎖,不需要顯式地調(diào)用鎖的獲取和釋放方法。

隱式鎖的實(shí)現(xiàn)主要有兩種:

  • 互斥鎖(Mutex):基于操作系統(tǒng)的互斥量(Mutex)實(shí)現(xiàn),通常由操作系統(tǒng)提供的底層機(jī)制來保證同一時(shí)刻只有一個(gè)線程可以持有鎖。
  • 內(nèi)部鎖(Intrinsic Lock):也稱為監(jiān)視器鎖(Monitor Lock),是Java對(duì)象的內(nèi)置鎖,每個(gè)Java對(duì)象都有一個(gè)關(guān)聯(lián)的監(jiān)視器鎖。通過 synchronized 關(guān)鍵字來獲取和釋放對(duì)象的監(jiān)視器鎖。

優(yōu)點(diǎn):

  • 使用簡(jiǎn)便:隱式鎖通常是由編程語言、運(yùn)行時(shí)庫(kù)或者虛擬機(jī)自動(dòng)管理的,不需要程序員手動(dòng)調(diào)用鎖的獲取和釋放方法,使用較為簡(jiǎn)便。
  • 自動(dòng)釋放:隱式鎖通常會(huì)在不需要的時(shí)候自動(dòng)釋放,從而減少了由于忘記釋放鎖而導(dǎo)致的死鎖等問題的風(fēng)險(xiǎn)。

缺點(diǎn):

  • 鎖定粒度較大:隱式鎖通常是在方法或者代碼塊級(jí)別上加鎖的,鎖定粒度較大,可能會(huì)導(dǎo)致性能下降或者并發(fā)度降低。
  • 功能較少:隱式鎖通常提供的功能較為有限,可能不足以滿足復(fù)雜的并發(fā)場(chǎng)景的需求。 顯式鎖

顯式鎖(Explicit Lock,又稱手動(dòng)鎖)

是通過Java中的 Lock 接口及其實(shí)現(xiàn)類來實(shí)現(xiàn)的,它提供了顯式地獲取鎖和釋放鎖的方法,例如 lock() unlock() 方法,需要在代碼中明確地調(diào)用這些方法來獲取和釋放鎖。

常見的顯式鎖實(shí)現(xiàn)包括:

  • ReentrantLock:可重入鎖,支持公平鎖和非公平鎖,并提供了豐富的特性如可中斷、超時(shí)、條件等。
  • ReentrantReadWriteLock:可重入讀寫鎖,支持多線程對(duì)共享資源的讀操作,以及獨(dú)占寫操作。
  • StampedLock:支持樂觀讀、悲觀讀和寫操作,并提供了樂觀讀的優(yōu)化。

優(yōu)點(diǎn):

  1. 可以提供更細(xì)粒度的控制:顯式鎖允許程序員手動(dòng)地控制鎖的獲取和釋放,從而可以在代碼中實(shí)現(xiàn)更細(xì)粒度的鎖定粒度,以滿足具體的需求。
  2. 提供更多的功能:顯式鎖通常提供了更多的功能,例如可重入鎖(Reentrant Lock)允許同一線程多次獲取同一把鎖,以避免死鎖;以及一些高級(jí)功能如條件變量(Condition)等,可以在復(fù)雜的并發(fā)場(chǎng)景中使用。
  3. 可以避免鎖的自動(dòng)釋放:顯式鎖通常不會(huì)自動(dòng)釋放,而需要手動(dòng)調(diào)用鎖的釋放方法,這可以避免鎖在不需要的時(shí)候自動(dòng)釋放,從而提供更好的控制。

缺點(diǎn):

  • 使用復(fù)雜:顯式鎖需要程序員手動(dòng)地調(diào)用鎖的獲取和釋放方法,需要更多的代碼和注意事項(xiàng),使用較為復(fù)雜。
  • 容易出錯(cuò):由于顯式鎖需要程序員手動(dòng)地管理鎖的獲取和釋放,容易出現(xiàn)錯(cuò)誤,例如忘記釋放鎖或者死鎖等問題。

悲觀鎖和樂觀鎖

樂觀鎖和悲觀鎖是以對(duì)共享資源的訪問方式來區(qū)分的。

悲觀鎖

悲觀鎖在并發(fā)環(huán)境中認(rèn)為數(shù)據(jù)隨時(shí)會(huì)被其他線程修改,因此每次在訪問數(shù)據(jù)時(shí)都會(huì)加鎖,直到操作完成后才釋放鎖。悲觀鎖適用于寫操作多、競(jìng)爭(zhēng)激烈的場(chǎng)景,比如多個(gè)線程同時(shí)對(duì)同一數(shù)據(jù)進(jìn)行修改或刪除操作的情況。悲觀鎖可以保證數(shù)據(jù)的一致性,避免臟讀、幻讀等問題的發(fā)生。悲觀鎖適用于讀少寫多的場(chǎng)景。

Java中常用的悲觀鎖是synchronized關(guān)鍵字和ReentrantLock類。

悲觀鎖存的問題

  1. 效率低:悲觀鎖需要獲取鎖才能進(jìn)行操作,當(dāng)有多個(gè)線程需要訪問同一份數(shù)據(jù)時(shí),每個(gè)線程都需要先獲取鎖,然后再進(jìn)行操作,如果鎖競(jìng)爭(zhēng)激烈,就會(huì)導(dǎo)致線程等待鎖的釋放,浪費(fèi)了大量的時(shí)間。
  2. 容易引起死鎖:悲觀鎖在獲取鎖的過程中,如果獲取不到就會(huì)一直等待,如果不同的線程都在等待對(duì)方釋放鎖,就會(huì)導(dǎo)致死鎖的情況出現(xiàn)。
  3. 可能會(huì)引起線程阻塞:當(dāng)某個(gè)線程獲取到鎖時(shí),其他線程需要等待,如果等待的時(shí)間過長(zhǎng),就會(huì)導(dǎo)致線程阻塞,影響應(yīng)用的性能。

樂觀鎖

樂觀鎖在并發(fā)環(huán)境中認(rèn)為數(shù)據(jù)一般情況下不會(huì)被其他線程修改,因此在訪問數(shù)據(jù)時(shí)不加鎖,而是在更新數(shù)據(jù)時(shí)進(jìn)行檢查。如果檢查到數(shù)據(jù)被其他線程修改,則放棄當(dāng)前操作,重新嘗試更新。樂觀鎖適用于讀操作多、寫操作少的場(chǎng)景,比如多個(gè)線程同時(shí)對(duì)同一數(shù)據(jù)進(jìn)行讀取操作的情況。樂觀鎖可以減少鎖的競(jìng)爭(zhēng),提高系統(tǒng)的并發(fā)性能。

Java中常用的樂觀鎖是基于CAS(Compare and Swap,比較和交換)算法實(shí)現(xiàn)的。

CAS操作包括三個(gè)操作數(shù):內(nèi)存地址V、舊的預(yù)期值A(chǔ)新的值B。CAS操作首先讀取內(nèi)存地址V中的值,如果該值等于舊的預(yù)期值A(chǔ),那么將內(nèi)存地址V中的值更新為新的值B;否則,不進(jìn)行任何操作。在更新過程中,如果有其他線程同時(shí)對(duì)該共享資源進(jìn)行了修改,那么CAS操作會(huì)失敗,此時(shí)需要重試更新操作。

樂觀鎖存在的問題

CAS雖然很?效的解決原?操作,但是CAS仍然存在三?問題:ABA問題,自旋時(shí)間過長(zhǎng)只能保證單個(gè)變量的原子性

  1. ABA問題:CAS算法在比較和替換時(shí)只考慮了值是否相等,而沒有考慮到值的版本信息。如果一個(gè)值在操作過程中被修改了兩次,從原值變成新值再變回原值,此時(shí)CAS會(huì)認(rèn)為值沒有發(fā)生變化,從而出現(xiàn)操作的錯(cuò)誤。為了解決ABA問題,可以在共享資源中增加版本號(hào),每次修改操作都將版本號(hào)加1,從而保證每次更新操作的唯一性。在更新數(shù)據(jù)時(shí)先讀取當(dāng)前版本號(hào),如果與自己持有的版本號(hào)相同,則可以更新數(shù)據(jù),否則更新失敗。版本號(hào)算法可以避免ABA問題,但需要維護(hù)版本號(hào),增加了代碼復(fù)雜度和內(nèi)存開銷。
  2. 自旋時(shí)間過長(zhǎng):由于CAS算法在失敗時(shí)會(huì)一直自旋,等待共享變量可用,如果共享變量一直不可用,就會(huì)出現(xiàn)自旋時(shí)間過長(zhǎng)的問題,浪費(fèi)CPU資源。
  3. 只能保證單個(gè)變量的原子性:CAS算法只能保證單個(gè)變量的原子性,如果需要多個(gè)變量的原子操作,就需要使用鎖等其他方式進(jìn)行保護(hù)。

公平鎖和非公平鎖

按照是否按照請(qǐng)求的順序來分配鎖,鎖分為公平鎖和非公平鎖。

公平鎖

公平鎖(Fair Lock)是指當(dāng)多個(gè)線程競(jìng)爭(zhēng)鎖時(shí),先到先得,后到后等,按照請(qǐng)求的先后順序來獲取鎖。這樣可以避免某些線程長(zhǎng)時(shí)間等待鎖,從而提高系統(tǒng)的公平性。但是,公平鎖也有一些缺點(diǎn),比如性能開銷較大,因?yàn)樾枰S護(hù)一個(gè)有序的等待隊(duì)列,并且可能導(dǎo)致更多的上下文切換。

優(yōu)點(diǎn):

  1. 公平性較好,避免了線程饑餓現(xiàn)象,每個(gè)線程都有公平的機(jī)會(huì)獲取鎖。
  2. 具有較高的線程公平性,適用于對(duì)公平性要求較高的場(chǎng)景。

缺點(diǎn):

  1. 公平鎖的實(shí)現(xiàn)較為復(fù)雜,可能會(huì)導(dǎo)致性能較低,因?yàn)樾枰l繁地切換線程和維護(hù)等待隊(duì)列。
  2. 在高并發(fā)場(chǎng)景下,可能會(huì)導(dǎo)致大量的線程切換和等待,影響性能。
  3. 可能引起死鎖:如果某個(gè)線程獲取鎖失敗而進(jìn)入等待狀態(tài),而鎖的持有者又在等待該線程的資源,就會(huì)出現(xiàn)死鎖的情況。

非公平鎖

非公平鎖(Unfair Lock)是指當(dāng)多個(gè)線程競(jìng)爭(zhēng)鎖時(shí),不一定按照請(qǐng)求的順序來分配鎖,而是由操作系統(tǒng)決定哪個(gè)線程先獲取鎖。這樣可以減少一些開銷,提高系統(tǒng)的吞吐量。

優(yōu)點(diǎn):

  1. 實(shí)現(xiàn)簡(jiǎn)單:非公平鎖的實(shí)現(xiàn)較為簡(jiǎn)單,通常性能較高,因?yàn)闊o需維護(hù)等待隊(duì)列和頻繁地切換線程。
  2. 性能高:由于非公平鎖不考慮線程的等待順序,所以在高并發(fā)環(huán)境下可以更快地獲取鎖,從而提高系統(tǒng)的處理能力。

缺點(diǎn):

  1. 不公平:非公平鎖會(huì)導(dǎo)致一些線程長(zhǎng)期無法獲取到鎖,而其他線程會(huì)一直占用鎖資源,這種情況會(huì)導(dǎo)致線程的饑餓現(xiàn)象,不公平性較高。
  2. 可能導(dǎo)致線程饑餓:如果一些線程一直占用鎖資源,而其他線程無法獲取鎖,則后者可能永遠(yuǎn)無法執(zhí)行,從而導(dǎo)致線程饑餓現(xiàn)象。
  3. 不利于資源調(diào)度:非公平鎖不考慮線程的等待時(shí)間,也就是說,一個(gè)剛剛進(jìn)入等待隊(duì)列的線程可能會(huì)比一個(gè)已經(jīng)等待很久的線程先獲得鎖,這種情況不利于資源的合理調(diào)度,容易導(dǎo)致一些線程長(zhǎng)時(shí)間處于等待狀態(tài)。

Java中有多種實(shí)現(xiàn)方式可以實(shí)現(xiàn)公平鎖和非公平鎖。其中最常用的是ReentrantLock類,它是一個(gè)可重入的互斥鎖,可以通過構(gòu)造函數(shù)傳入一個(gè)boolean類型的值來指定是否為公平鎖。例如:

ReentrantLock fairLock = new ReentrantLock(true); //創(chuàng)建一個(gè)公平鎖
ReentrantLock unfairLock = new ReentrantLock(false); //創(chuàng)建一個(gè)非公平鎖

除了ReentrantLock之外,還有其他一些類可以實(shí)現(xiàn)公平鎖和非公平鎖,比如 synchronized 關(guān)鍵字是非公平鎖、 Semaphore(信號(hào)量)、ReadWriteLock(讀寫鎖)、StampedLock(樂觀鎖)等。

下面是一段ReentrantLock公平鎖的簡(jiǎn)單實(shí)現(xiàn):

import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
    private static final ReentrantLock lock = new ReentrantLock(true); // true 表示使用公平鎖
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " get the lock.");
                } finally {
                    lock.unlock();
                }
            }
        }, "Thread-A").start();
        new Thread(() -> {
            while (true) {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " get the lock.");
                } finally {
                    lock.unlock();
                }
            }
        }, "Thread-B").start();
    }
}

運(yùn)行結(jié)果

從運(yùn)行結(jié)果可以看到Thread-A和Thread-B是交替運(yùn)行獲取鎖的,如果想使用非公平鎖只需要 new ReentrantLock(false) ,想嘗試的可以自己試一下。

選擇公平鎖還是非公平鎖應(yīng)該根據(jù)具體的業(yè)務(wù)需求和性能要求來進(jìn)行權(quán)衡。

如果對(duì)公平性要求較高,并且能容忍性能的一定降低,可以選擇公平鎖;

如果對(duì)性能要求較高,并且能容忍一定程度的線程不公平現(xiàn)象。

可重入鎖和非可重入鎖

根據(jù)是否支持同一線程對(duì)同一鎖的重復(fù)獲取進(jìn)行分類,分為可重入鎖和非可重入鎖。

可重入鎖

可重入鎖(Reentrant Lock)允許同一線程多次獲取同一鎖,并且不會(huì)造成死鎖。

可重入鎖實(shí)現(xiàn)了鎖的遞歸性,同一線程可以重復(fù)獲取鎖而不會(huì)被阻塞,因?yàn)殒i會(huì)記錄持有鎖的線程和鎖的重入次數(shù)。

在線程持有鎖的情況下,再次請(qǐng)求該鎖時(shí),如果鎖是可重入的,線程會(huì)成功獲取鎖而不會(huì)被阻塞。

常見的可重入鎖有:

  1. java.util.concurrent.ReentrantLock 類:這是 Java 并發(fā)包中提供的一個(gè)可重入鎖實(shí)現(xiàn),提供了豐富的功能,如支持公平和非公平鎖、可設(shè)置超時(shí)時(shí)間、支持多個(gè)條件等。
  2. java.util.concurrent.ReentrantReadWriteLock 類:這是 Java 并發(fā)包中提供的一個(gè)可重入讀寫鎖實(shí)現(xiàn),允許多個(gè)線程同時(shí)讀取共享資源,但在寫操作時(shí)需要互斥。
  3. Java 的內(nèi)置鎖 synchronized 關(guān)鍵字是可重入的。

下面通過一段代碼加深一下對(duì)可重入鎖的理解:

import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
    private ReentrantLock lock = new ReentrantLock(); // 創(chuàng)建一個(gè)可重入鎖
    public void doSomething() {
        lock.lock(); // 加鎖
        try {
            // 這里是需要加鎖的代碼
            System.out.println("線程 " + Thread.currentThread().getName() + " 獲取到鎖");
            doSomethingElse(); // 可以再次調(diào)用自己的方法,多次獲取鎖,不會(huì)導(dǎo)致死鎖
        } finally {
            lock.unlock(); // 解鎖
            System.out.println("線程 " + Thread.currentThread().getName() + " 釋放鎖");
        }
    }
    public void doSomethingElse() {
        lock.lock(); // 再次加鎖,同一個(gè)線程可以多次獲取同一個(gè)鎖
        try {
            // 這里是需要加鎖的代碼
            System.out.println("線程 " + Thread.currentThread().getName() + " 獲取到鎖(再次獲?。?);
        } finally {
            lock.unlock(); // 解鎖
            System.out.println("線程 " + Thread.currentThread().getName() + " 釋放鎖(再次獲取)");
        }
    }
    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                example.doSomething();
            }, "Thread " + i).start();
        }
    }
}

結(jié)果:

線程 Thread 1 獲取到鎖
線程 Thread 1 獲取到鎖(再次獲?。?br />線程 Thread 1 釋放鎖(再次獲?。?br />線程 Thread 1 釋放鎖
線程 Thread 2 獲取到鎖
線程 Thread 2 獲取到鎖(再次獲?。?br />線程 Thread 2 釋放鎖(再次獲?。?br />線程 Thread 2 釋放鎖
線程 Thread 3 獲取到鎖
線程 Thread 3 獲取到鎖(再次獲?。?br />線程 Thread 3 釋放鎖(再次獲取)
線程 Thread 3 釋放鎖

在上面的示例中,我們使用了ReentrantLock來創(chuàng)建一個(gè)可重入鎖,并通過 lock() 方法加鎖,通過 unlock() 方法解鎖。

doSomething() 方法中,我們可以多次獲取同一個(gè)鎖,而不會(huì)導(dǎo)致死鎖。

這就是可重入鎖的特性,使得同一個(gè)線程在持有鎖的情況下可以繼續(xù)獲取鎖,從而避免了死鎖的可能性。

優(yōu)點(diǎn):

  1. 支持線程的重復(fù)獲取:同一個(gè)線程可以多次獲取同一個(gè)鎖,避免了死鎖的可能性。
  2. 支持公平和非公平鎖:可以根據(jù)需求選擇公平或非公平鎖,靈活性高。
  3. 提供了豐富的功能:如可設(shè)置超時(shí)時(shí)間、支持多個(gè)條件等,使得鎖的使用更加靈活。

缺點(diǎn):

  1. 復(fù)雜性高:相比于內(nèi)置鎖 synchronized 關(guān)鍵字,可重入鎖的使用復(fù)雜度較高,需要手動(dòng)加鎖和解鎖,容易出錯(cuò)。
  2. 性能相對(duì)較低:相較于內(nèi)置鎖 synchronized 關(guān)鍵字,可重入鎖的性能可能較低,因?yàn)樗峁┝烁嗟墓δ芎挽`活性。

非可重入鎖

非可重入鎖(Non-reentrant Lock)不允許同一線程多次獲取同一鎖,否則會(huì)造成死鎖。

非可重入鎖實(shí)現(xiàn)了簡(jiǎn)單的互斥,但不支持同一線程對(duì)同一鎖的重復(fù)獲取。

缺點(diǎn):

  1. 不支持線程的重復(fù)獲取:同一個(gè)線程無法多次獲取同一個(gè)鎖,容易導(dǎo)致死鎖。
  2. 功能較為簡(jiǎn)單:非可重入鎖通常只提供了基本的加鎖和解鎖功能,缺乏靈活性和豐富的功能。

是的,你沒有看錯(cuò)非可重入鎖沒有優(yōu)點(diǎn)。

非可重入鎖在 Java 中沒有現(xiàn)成的實(shí)現(xiàn)。非可重入鎖通常是通過自定義的鎖機(jī)制來實(shí)現(xiàn)的,但在 Java 標(biāo)準(zhǔn)庫(kù)中并沒有提供非可重入鎖的實(shí)現(xiàn)。需要注意的是,使用非可重入鎖時(shí)需要非常小心,因?yàn)樗赡軐?dǎo)致死鎖或其他并發(fā)問題。

獨(dú)占鎖和共享鎖

獨(dú)占鎖和共享鎖是根據(jù)對(duì)鎖的獲取方式以及鎖對(duì)資源的訪問權(quán)限進(jìn)行分類的。

獨(dú)占鎖

獨(dú)占鎖(Exclusive Lock),也稱為排它鎖(Exclusive Lock)、互斥鎖(Mutex Lock),它在同一時(shí)刻只允許一個(gè)線程獲取鎖并且獨(dú)占資源,其他線程需要等待該鎖釋放后才能獲取鎖并訪問資源。獨(dú)占鎖是一種排它性的鎖,它確保了在同一時(shí)刻只有一個(gè)線程可以執(zhí)行臨界區(qū)內(nèi)的代碼,從而避免了并發(fā)訪問導(dǎo)致的競(jìng)態(tài)條件。

獨(dú)占鎖的實(shí)現(xiàn):

  • synchronized 關(guān)鍵字:Java 中的 synchronized 關(guān)鍵字可以用來實(shí)現(xiàn)獨(dú)占鎖。synchronized 可以修飾方法、代碼塊或者類,通過獲取對(duì)象的監(jiān)視器(Monitor)來實(shí)現(xiàn)對(duì)資源的獨(dú)占訪問。
  • ReentrantLock 類:Java 提供了 ReentrantLock 類,它是一個(gè)可重入的獨(dú)占鎖,可以通過調(diào)用 lock() unlock() 方法來獲取和釋放鎖。

優(yōu)點(diǎn):

  1. 獨(dú)占鎖可以確保在任何時(shí)候只有一個(gè)線程可以訪問被鎖定的資源,避免了多個(gè)線程之間的競(jìng)爭(zhēng)和沖突。
  2. 獨(dú)占鎖通常比較簡(jiǎn)單,易于實(shí)現(xiàn)和使用,適用于對(duì)資源進(jìn)行獨(dú)占性操作的場(chǎng)景。

缺點(diǎn):

  1. 獨(dú)占鎖可能導(dǎo)致性能下降,因?yàn)楫?dāng)一個(gè)線程獲得了獨(dú)占鎖后,其他線程必須等待鎖釋放才能訪問資源,可能導(dǎo)致線程間的競(jìng)爭(zhēng)和爭(zhēng)用。
  2. 獨(dú)占鎖可能導(dǎo)致死鎖,如果一個(gè)線程持有了獨(dú)占鎖而沒有釋放,其他線程無法獲取該鎖,可能導(dǎo)致死鎖現(xiàn)象。

共享鎖

共享鎖(Shared Lock)也稱為讀鎖(Read Lock),它允許多個(gè)線程同時(shí)讀取共享資源,但在寫入共享資源時(shí)會(huì)阻塞其他線程的讀和寫操作。

簡(jiǎn)單來說,共享鎖允許多個(gè)線程同時(shí)讀取共享資源,但在寫入時(shí)需要獨(dú)占資源,防止其他線程同時(shí)進(jìn)行寫操作,從而確保數(shù)據(jù)的一致性和完整性。

舉個(gè)生活中的例子來解釋,假設(shè)你和你的朋友在圖書館里讀書,你們都可以同時(shí)讀取同一本書(共享資源),這時(shí)候圖書館采用的就是共享鎖的機(jī)制。

但是,當(dāng)你想要在書中做筆記(寫入共享資源)時(shí),你需要獨(dú)占書本,防止其他人同時(shí)進(jìn)行修改,這時(shí)候圖書館就會(huì)對(duì)你進(jìn)行阻塞,直到你完成筆記并釋放書本的獨(dú)占鎖。

共享鎖的實(shí)現(xiàn):

  1. ReadWriteLock 接口:Java 提供了 ReadWriteLock 接口,它定義了讀鎖和寫鎖的兩種鎖類型,允許多個(gè)線程同時(shí)獲取讀鎖,但在有寫鎖時(shí)禁止獲取讀鎖,以保護(hù)共享資源的一致性??梢酝ㄟ^調(diào)用 readLock() writeLock() 方法來獲取讀鎖和寫鎖。
  2. ReentrantReadWriteLock 類:Java 提供了 ReentrantReadWriteLock 類,它是 ReadWriteLock 接口的實(shí)現(xiàn)類,可以通過調(diào)用 readLock() writeLock() 方法來獲取讀鎖和寫鎖。

可以發(fā)現(xiàn)Java中對(duì)共享鎖實(shí)現(xiàn)是讀寫鎖(ReadWriteLock),讀寫鎖是一種特殊的共享鎖,它將對(duì)共享數(shù)據(jù)的訪問劃分為讀訪問和寫訪問兩種類型,讀訪問可以并發(fā)執(zhí)行,寫訪問需要獨(dú)占式地進(jìn)行。讀寫鎖允許多個(gè)線程同時(shí)讀共享數(shù)據(jù),而對(duì)于寫操作,只有一個(gè)線程能夠進(jìn)行寫操作,寫操作執(zhí)行期間,其它所有的讀操作和寫操作都被阻塞。

讀寫鎖有兩種鎖類型:讀鎖和寫鎖。當(dāng)有多個(gè)線程要訪問共享數(shù)據(jù)時(shí),如果只有讀操作,線程可以同時(shí)持有讀鎖;但如果有寫操作,任何線程都不能持有寫鎖或者讀鎖,直到寫操作完成。

優(yōu)點(diǎn):

  1. 共享鎖可以允許多個(gè)線程同時(shí)讀取共享資源,提高了并發(fā)性能。
  2. 共享鎖在讀多寫少的場(chǎng)景中通常比較適用,可以提供更好的性能和并發(fā)控制。

缺點(diǎn):

  1. 共享鎖不能阻止其他線程獲取寫鎖,可能導(dǎo)致寫鎖饑餓現(xiàn)象,即寫鎖一直無法獲取而導(dǎo)致一直等待。
  2. 共享鎖通常較復(fù)雜,需要更復(fù)雜的實(shí)現(xiàn)和管理,可能增加了編程和維護(hù)的復(fù)雜性。

以下是一個(gè)共享鎖的代碼示例:

import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SharedLockExample {
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static final String[] data = new String[10];
    public static void main(String[] args) throws InterruptedException {
        Thread writer1 = new Thread(() -> {
            lock.writeLock().lock();
            try {
                // 寫操作,這里簡(jiǎn)單地用數(shù)組來模擬
                data[0] = "writer1 data";
                Thread.sleep(1000); // 模擬寫操作需要一定時(shí)間
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });
        Thread writer2 = new Thread(() -> {
            lock.writeLock().lock();
            try {
                // 再次寫操作
                data[0] = "writer2 data";
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });
        Thread reader1 = new Thread(() -> {
            lock.readLock().lock();
            try {
                // 讀操作,這里簡(jiǎn)單地輸出數(shù)組的第一個(gè)元素
                System.out.println("Reader 1 read data: " + data[0]);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        });
        Thread reader2 = new Thread(() -> {
            lock.readLock().lock();
            try {
                // 再次讀操作
                System.out.println("Reader 2 read data: " + data[0]);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        });
        // 啟動(dòng)寫線程
        writer1.start();
        writer2.start();
        // 啟動(dòng)讀線程
        reader1.start();
        reader2.start();
        // 等待所有線程結(jié)束
        writer1.join();
        writer2.join();
        reader1.join();
        reader2.join();
    }
}

運(yùn)行結(jié)果

在這個(gè)例子中,我們使用了 ReentrantReadWriteLock 來實(shí)現(xiàn)共享鎖,這個(gè)鎖可以允許多個(gè)線程同時(shí)獲得讀鎖,但只允許一個(gè)線程獲得寫鎖,以保證數(shù)據(jù)的一致性。我們啟動(dòng)了兩個(gè)寫線程和兩個(gè)讀線程,從運(yùn)行結(jié)果中可以看到讀線程是可以同時(shí)進(jìn)行的,而寫線程則是需要等待前一個(gè)寫線程完成才能進(jìn)行的。這樣,就可以保證在數(shù)據(jù)讀取的同時(shí),也能保證數(shù)據(jù)的安全性。

獨(dú)占鎖和共享鎖通常用于在多線程環(huán)境中對(duì)共享資源進(jìn)行不同的操作,獨(dú)占鎖用于保護(hù)對(duì)資源的獨(dú)占性操作,而共享鎖則允許多個(gè)線程同時(shí)讀取資源而不互斥。這兩種鎖的選擇取決于應(yīng)用場(chǎng)景和對(duì)資源訪問的要求。在 Java 中,ReentrantLock 類可以用于實(shí)現(xiàn)獨(dú)占鎖和共享鎖的功能,具體通過 lock() 方法獲取獨(dú)占鎖,通過 readLock() 方法獲取共享鎖。

偏向鎖、輕量級(jí)鎖和重量級(jí)鎖

偏向鎖、輕量級(jí)鎖和重量級(jí)鎖是 Java 中的三種鎖狀態(tài),它們是根據(jù)鎖在多線程環(huán)境中的競(jìng)爭(zhēng)狀態(tài)和性能優(yōu)化的程度進(jìn)行分類的。

偏向鎖

偏向鎖是為了解決無競(jìng)爭(zhēng)情況下的高性能問題而產(chǎn)生的,適用于只有一個(gè)線程訪問鎖對(duì)象的場(chǎng)景。當(dāng)鎖對(duì)象第一次被訪問時(shí),JVM會(huì)將鎖對(duì)象頭部的標(biāo)記設(shè)置為偏向鎖。之后該線程再次訪問鎖對(duì)象時(shí),無需再次獲取鎖,直接進(jìn)入同步語句塊執(zhí)行。當(dāng)其他線程試圖獲取鎖時(shí),會(huì)檢查偏向鎖的標(biāo)記,如果鎖被偏向于其他線程,則會(huì)撤銷偏向鎖,升級(jí)為輕量級(jí)鎖。

偏向鎖優(yōu)點(diǎn):

  • 提供了最快的鎖獲取和釋放操作,適用于對(duì)象在大多數(shù)時(shí)間內(nèi)都只被一個(gè)線程訪問的場(chǎng)景,減少了多線程競(jìng)爭(zhēng)的開銷。
  • 避免了多線程競(jìng)爭(zhēng)對(duì)性能的影響,減少了鎖的競(jìng)爭(zhēng)開銷,提高了并發(fā)性能。

偏向鎖缺點(diǎn):

  • 需要在每個(gè)對(duì)象頭上額外存儲(chǔ)偏向鎖標(biāo)記,占用了額外的內(nèi)存空間。
  • 對(duì)象可能在某一時(shí)刻被多個(gè)線程訪問,導(dǎo)致偏向鎖升級(jí)為輕量級(jí)鎖或重量級(jí)鎖,增加了額外的鎖升級(jí)開銷。
  • 適用場(chǎng)景有限,只適合對(duì)象在大多數(shù)時(shí)間內(nèi)只被一個(gè)線程訪問的場(chǎng)景,對(duì)于高度并發(fā)的場(chǎng)景可能性能不佳。

輕量級(jí)鎖

輕量級(jí)鎖是為了解決競(jìng)爭(zhēng)不激烈的情況下鎖開銷過大的問題。當(dāng)?shù)谝粋€(gè)線程獲取輕量級(jí)鎖時(shí),JVM會(huì)把鎖對(duì)象頭部的信息復(fù)制到線程的棧幀中,稱為鎖記錄。之后,其他線程也嘗試獲取該鎖對(duì)象時(shí),JVM會(huì)先檢查鎖記錄是否被占用,如果沒有被占用,則表示該鎖對(duì)象沒有被鎖定,將鎖對(duì)象頭部的標(biāo)記改為偏向鎖,并將鎖記錄中的線程ID設(shè)置為當(dāng)前線程的ID。如果鎖記錄已經(jīng)被占用,則將鎖升級(jí)為重量級(jí)鎖。

輕量級(jí)鎖優(yōu)點(diǎn):

  • 采用自旋的方式,避免了線程的阻塞和喚醒操作,減少了線程上下文切換的開銷。
  • 在鎖競(jìng)爭(zhēng)較輕的場(chǎng)景中,性能較好,對(duì)于多線程競(jìng)爭(zhēng)不激烈的場(chǎng)景可以提供一定的性能優(yōu)化。

輕量級(jí)鎖缺點(diǎn):

  • 自旋的方式會(huì)增加CPU的消耗,對(duì)于鎖競(jìng)爭(zhēng)較激烈的場(chǎng)景,自旋可能會(huì)導(dǎo)致性能下降。
  • 輕量級(jí)鎖需要通過CAS(比較并交換)操作來進(jìn)行加鎖和解鎖,CAS操作失敗時(shí)會(huì)升級(jí)為重量級(jí)鎖,增加了額外的鎖升級(jí)開銷。
  • 在高度并發(fā)的場(chǎng)景中,輕量級(jí)鎖的性能可能不如其他鎖。

重量級(jí)鎖

重量級(jí)鎖是多個(gè)線程競(jìng)爭(zhēng)同一個(gè)鎖對(duì)象時(shí)采用的策略,JVM會(huì)把鎖對(duì)象頭部信息更換為指向一個(gè)互斥量(Monitor)的指針,并阻塞等待獲取鎖的線程,直到鎖被釋放。由于需要操作系統(tǒng)層面的線程阻塞與喚醒,因此重量級(jí)鎖的效率相對(duì)偏向鎖和輕量級(jí)鎖來說較低。

重量級(jí)鎖優(yōu)點(diǎn):

  • 提供了穩(wěn)定的鎖保護(hù),適用于多線程競(jìng)爭(zhēng)激烈的場(chǎng)景,保證了線程安全性。
  • 不會(huì)占用額外的對(duì)象頭空間,對(duì)內(nèi)存占用較小。

重量級(jí)鎖缺點(diǎn):

  • 需要進(jìn)行線程的阻塞和喚醒操作,增加了線程上下文切換的開銷。
  • 鎖競(jìng)爭(zhēng)較激烈時(shí),可能導(dǎo)致大量線程阻塞,從而降低系統(tǒng)的并發(fā)性能。

在實(shí)現(xiàn)上,偏向鎖和輕量級(jí)鎖都是通過在對(duì)象頭中記錄鎖的標(biāo)記來實(shí)現(xiàn)的。重量級(jí)鎖則是使用了操作系統(tǒng)提供的互斥量機(jī)制,在用戶態(tài)和內(nèi)核態(tài)之間切換,效率較低。

在Java中,偏向鎖、輕量級(jí)鎖和重量級(jí)鎖之間可以進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換的條件如下:

  1. 偏向鎖轉(zhuǎn)輕量級(jí)鎖: 當(dāng)有一個(gè)線程訪問對(duì)象并獲取了偏向鎖之后,如果另一個(gè)線程嘗試訪問同一個(gè)對(duì)象并請(qǐng)求獲取鎖,偏向鎖會(huì)先嘗試撤銷偏向鎖,然后將鎖狀態(tài)升級(jí)為輕量級(jí)鎖。這通常發(fā)生在多個(gè)線程在對(duì)同一對(duì)象進(jìn)行并發(fā)訪問時(shí)。
  2. 輕量級(jí)鎖轉(zhuǎn)偏向鎖: 當(dāng)持有輕量級(jí)鎖的線程執(zhí)行完同步代碼塊后,會(huì)釋放鎖。如果在釋放鎖之前,沒有其他線程嘗試獲取同一把鎖,那么輕量級(jí)鎖會(huì)恢復(fù)為偏向鎖狀態(tài),將鎖標(biāo)記為偏向鎖,并記錄獲得偏向鎖的線程ID,以便后續(xù)訪問時(shí)可以直接偏向給定線程。
  3. 輕量級(jí)鎖轉(zhuǎn)重量級(jí)鎖: 當(dāng)持有輕量級(jí)鎖的線程無法成功自旋獲取鎖時(shí),會(huì)將鎖升級(jí)為重量級(jí)鎖。自旋是指線程在獲取鎖時(shí)會(huì)嘗試一段時(shí)間的忙等待,避免線程阻塞和喚醒帶來的開銷。如果自旋超過一定次數(shù)或達(dá)到某個(gè)條件時(shí)仍未獲取到鎖,輕量級(jí)鎖會(huì)升級(jí)為重量級(jí)鎖。
  4. 重量級(jí)鎖轉(zhuǎn)輕量級(jí)鎖: 當(dāng)持有重量級(jí)鎖的線程釋放鎖時(shí),如果沒有其他線程在等待獲取鎖,鎖會(huì)降級(jí)為輕量級(jí)鎖,從而可以讓其他線程嘗試通過自旋快速獲取鎖,減少線程阻塞和喚醒帶來的開銷。

鎖的狀態(tài)轉(zhuǎn)換在Java虛擬機(jī)中是自動(dòng)進(jìn)行的,根據(jù)線程對(duì)鎖的訪問情況和并發(fā)競(jìng)爭(zhēng)情況自動(dòng)切換鎖的類型,以優(yōu)化性能和保障線程安全。

分段鎖

分段鎖是一種鎖策略,它將鎖分為多個(gè)部分,每個(gè)部分有獨(dú)立的鎖,用于控制對(duì)應(yīng)部分的并發(fā)訪問。一般情況下,分段鎖被應(yīng)用于并發(fā)讀寫場(chǎng)景,將數(shù)據(jù)分為多個(gè)段,每個(gè)段對(duì)應(yīng)一個(gè)鎖,多個(gè)線程同時(shí)對(duì)不同的段進(jìn)行讀寫操作,以此來提高并發(fā)訪問性能。

分段鎖常用于高并發(fā)讀寫操作,例如:ConcurrentHashMap 的實(shí)現(xiàn)。ConcurrentHashMap 采用了分段鎖的方式來保證并發(fā)的安全性,將整個(gè) ConcurrentHashMap 的數(shù)據(jù)分成了多個(gè)段,每個(gè)段使用獨(dú)立的鎖進(jìn)行控制。這種方式有效地提高了 ConcurrentHashMap 的并發(fā)讀寫能力。

Java中有多個(gè)分段鎖的實(shí)現(xiàn),其中最常見的包括:

  1. ConcurrentHashMapConcurrentHashMap是一種高效的線程安全的哈希表,它使用了分段鎖來保證線程安全性和并發(fā)性。ConcurrentHashMap將整個(gè)哈希表分成多個(gè)段,每個(gè)段都是一個(gè)獨(dú)立的哈希表,擁有自己的鎖,多個(gè)線程可以同時(shí)訪問不同的段,從而實(shí)現(xiàn)了更好的并發(fā)性。
  2. ReentrantReadWriteLockReentrantReadWriteLock是一種讀寫鎖,它使用了分段鎖的思想。ReentrantReadWriteLock內(nèi)部維護(hù)了兩個(gè)鎖,一個(gè)讀鎖和一個(gè)寫鎖,多個(gè)線程可以同時(shí)獲取讀鎖,但只有一個(gè)線程能夠獲取寫鎖。讀寫鎖的實(shí)現(xiàn)基于分段鎖,將整個(gè)數(shù)據(jù)結(jié)構(gòu)分成多個(gè)段,每個(gè)段都可以被多個(gè)線程同時(shí)讀取,但是只有一個(gè)線程可以進(jìn)行寫操作。
  3. Striped64Striped64是Java中一種分段鎖的實(shí)現(xiàn),它主要用于并發(fā)計(jì)數(shù)器的實(shí)現(xiàn)。Striped64內(nèi)部維護(hù)了一個(gè)數(shù)組,數(shù)組的每個(gè)元素都是一個(gè)計(jì)數(shù)器,每個(gè)線程訪問時(shí)都會(huì)根據(jù)某種算法計(jì)算出一個(gè)索引,然后加鎖對(duì)應(yīng)的計(jì)數(shù)器進(jìn)行累加操作。
  4. StampedLockStampedLock是一種基于分段鎖的樂觀讀寫鎖。StampedLock內(nèi)部維護(hù)了多個(gè)段,每個(gè)段都對(duì)應(yīng)一個(gè)寫鎖和多個(gè)讀鎖。在讀操作時(shí),StampedLock使用了樂觀讀的方式,允許多個(gè)線程同時(shí)讀取數(shù)據(jù),而寫操作時(shí)則需要獲取獨(dú)占寫鎖。

下面是一個(gè)使用ConcurrentHashMap實(shí)現(xiàn)分段鎖的示例代碼:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SegmentLockMap<K, V> {
    private final int segmentsCount;
    private final ConcurrentHashMap<K, V>[] segments;
    private final Lock[] locks;
    public SegmentLockMap(int segmentsCount) {
        this.segmentsCount = segmentsCount;
        this.segments = new ConcurrentHashMap[segmentsCount];
        this.locks = new ReentrantLock[segmentsCount];
        for (int i = 0; i < segmentsCount; i++) {
            this.segments[i] = new ConcurrentHashMap<>();
            this.locks[i] = new ReentrantLock();
        }
    }
    private int getSegmentIndex(K key) {
        return Math.abs(key.hashCode() % segmentsCount);
    }
    public void put(K key, V value) {
        int segmentIndex = getSegmentIndex(key);
        locks[segmentIndex].lock();
        try {
            segments[segmentIndex].put(key, value);
        } finally {
            locks[segmentIndex].unlock();
        }
    }
    public V get(K key) {
        int segmentIndex = getSegmentIndex(key);
        locks[segmentIndex].lock();
        try {
            return segments[segmentIndex].get(key);
        } finally {
            locks[segmentIndex].unlock();
        }
    }
    public void clear() {
        for (int i = 0; i < segmentsCount; i++) {
            locks[i].lock();
            try {
                segments[i].clear();
            } finally {
                locks[i].unlock();
            }
        }
    }
}

這個(gè)類將數(shù)據(jù)分為多個(gè)段,每個(gè)段有一個(gè)獨(dú)立的鎖來保證線程安全。在put和get操作時(shí),先根據(jù)key值計(jì)算出對(duì)應(yīng)的段的索引,然后獲取該段的獨(dú)立鎖,最后對(duì)該段的數(shù)據(jù)進(jìn)行put或get操作。同時(shí),clear操作會(huì)遍歷所有的段,并分別獲取每個(gè)段的獨(dú)立鎖,確保清除操作的線程安全。

分段鎖的優(yōu)點(diǎn):

  • 減小鎖的粒度:通過將一個(gè)大的鎖分解為多個(gè)小的鎖,可以使并發(fā)程度更高,降低鎖的粒度,避免出現(xiàn)單點(diǎn)瓶頸,從而提高了系統(tǒng)的并發(fā)性能。
  • 減少鎖沖突:由于每個(gè)小鎖只鎖定一部分?jǐn)?shù)據(jù),因此在多線程并發(fā)操作時(shí),不同線程之間很可能不會(huì)產(chǎn)生鎖沖突,減少了線程的等待時(shí)間,提高了系統(tǒng)的并發(fā)度。
  • 提高系統(tǒng)的可伸縮性:分段鎖可以將整個(gè)系統(tǒng)分解為多個(gè)獨(dú)立的模塊,從而提高了系統(tǒng)的可伸縮性和擴(kuò)展性,便于系統(tǒng)的水平擴(kuò)展和集群部署。

分段鎖的缺點(diǎn):

  • 增加了鎖的管理復(fù)雜度:由于需要管理多個(gè)鎖,因此增加了鎖的管理復(fù)雜度,需要更多的內(nèi)存空間來維護(hù)鎖的信息,同時(shí)也需要更復(fù)雜的鎖協(xié)調(diào)機(jī)制,保證各個(gè)鎖之間的一致性和可靠性。
  • 可能會(huì)導(dǎo)致線程饑餓:如果分段不合理或者某些分段訪問頻率過高,可能會(huì)導(dǎo)致某些線程被阻塞,無法獲得鎖資源,從而導(dǎo)致線程饑餓問題。
  • 可能會(huì)降低并發(fā)度:由于需要管理多個(gè)鎖,可能會(huì)導(dǎo)致鎖的競(jìng)爭(zhēng)變得更加激烈,從而降低系統(tǒng)的并發(fā)度。此外,分段鎖的實(shí)現(xiàn)難度較大,需要合理設(shè)計(jì)分段策略和鎖協(xié)調(diào)機(jī)制,增加了系統(tǒng)的開發(fā)和維護(hù)成本。
  • 內(nèi)存占用:每個(gè)分段都需要使用額外的內(nèi)存空間來保存鎖信息和數(shù)據(jù),因此會(huì)增加系統(tǒng)的內(nèi)存占用。

在使用分段鎖時(shí),需要根據(jù)具體的應(yīng)用場(chǎng)景,進(jìn)行合理的鎖設(shè)計(jì)和鎖的粒度劃分,避免鎖的競(jìng)爭(zhēng)過于激烈或者鎖的粒度過大導(dǎo)致的性能問題。同時(shí),在多線程編程中,使用分段鎖時(shí)需要注意死鎖的問題。

自旋鎖

自旋鎖(Spin Lock)是一種忙等待的鎖。因?yàn)榫€程在獲取鎖時(shí)會(huì)循環(huán)等待,因此它不會(huì)主動(dòng)放棄CPU,而是一直占用CPU資源,直到獲取到鎖并完成相應(yīng)的操作后才會(huì)釋放CPU資源。自旋鎖適用于鎖的持有時(shí)間比較短且競(jìng)爭(zhēng)不激烈的情況下,可以減少線程上下文切換的開銷。

自旋鎖通常由一個(gè)標(biāo)志變量組成,線程在獲取自旋鎖時(shí)會(huì)循環(huán)檢查這個(gè)標(biāo)志變量,如果發(fā)現(xiàn)被占用,則不斷循環(huán)等待,直到標(biāo)志變量變?yōu)榭捎脿顟B(tài)才能獲取鎖。

下面是一個(gè)簡(jiǎn)單的自旋鎖代碼示例,使用Java中的AtomicBoolean實(shí)現(xiàn):

import java.util.concurrent.atomic.AtomicBoolean;
public class SpinLock {
    private AtomicBoolean lock = new AtomicBoolean(false);
    public void lock() {
        boolean acquiredLock = false;
        while (!acquiredLock) {
            acquiredLock = lock.compareAndSet(false, true);
        }
    }
    public void unlock() {
        lock.set(false);
    }
}

在上面的代碼中,我們使用AtomicBoolean來表示鎖狀態(tài),通過調(diào)用 compareAndSet() 方法實(shí)現(xiàn)了自旋。在 lock() 方法中,如果當(dāng)前鎖已經(jīng)被占用,那么就會(huì)一直自旋,直到獲得鎖為止;在 unlock() 方法中,我們將鎖狀態(tài)設(shè)置為false,表示釋放鎖。

自旋鎖的優(yōu)點(diǎn)是:

  • 自旋鎖適用于保護(hù)非常短小的代碼臨界區(qū),自旋鎖的效率比較高,不需要調(diào)度線程和上下文切換的開銷,從而提高系統(tǒng)的吞吐量。
  • 自旋鎖不會(huì)引起上下文切換和線程調(diào)度,因此對(duì)多處理器系統(tǒng)的性能影響較小。

自旋鎖的缺點(diǎn)是:

  • 自旋鎖適用于臨界區(qū)比較短小的情況,如果臨界區(qū)比較大,自旋鎖將占用較長(zhǎng)時(shí)間,影響系統(tǒng)的吞吐量。
  • 自旋鎖采用忙等待的方式,會(huì)占用 CPU 時(shí)間,如果等待時(shí)間過長(zhǎng),會(huì)浪費(fèi) CPU 資源。
  • 自旋鎖在單 CPU 系統(tǒng)中,由于自旋鎖一直占用 CPU 不釋放,因此其他線程無法執(zhí)行,會(huì)出現(xiàn)線程饑餓現(xiàn)象。

死鎖

死鎖不是一種具體的鎖結(jié)構(gòu),它是多線程并發(fā)編程中的一種特定情況,它通常由于資源的競(jìng)爭(zhēng)和同步不當(dāng)導(dǎo)致的。

死鎖是指多個(gè)線程之間發(fā)生了互相等待對(duì)方持有的鎖而無法繼續(xù)執(zhí)行的狀態(tài)。具體而言,當(dāng)多個(gè)線程持有某些鎖,并且每個(gè)線程都在等待其他線程持有的鎖釋放,從而形成了一個(gè)循環(huán)依賴關(guān)系,導(dǎo)致線程都無法繼續(xù)執(zhí)行下去,稱為死鎖。

死鎖通常在以下情況下可能發(fā)生:

  1. 互斥資源:當(dāng)多個(gè)線程需要互斥地訪問某些資源,而這些資源在同一時(shí)間只能被一個(gè)線程占用時(shí),如果多個(gè)線程之間相互等待對(duì)方釋放資源,就可能發(fā)生死鎖。
  2. 競(jìng)爭(zhēng)資源:當(dāng)多個(gè)線程競(jìng)爭(zhēng)有限的資源時(shí),而且在資源分配和釋放時(shí)沒有足夠的同步和協(xié)調(diào),也可能導(dǎo)致死鎖。例如,多個(gè)線程同時(shí)競(jìng)爭(zhēng)一些全局鎖或系統(tǒng)資源時(shí),如果競(jìng)爭(zhēng)不當(dāng),可能會(huì)導(dǎo)致死鎖。
  3. 循環(huán)等待:當(dāng)多個(gè)線程之間形成了循環(huán)等待資源的情況時(shí),也可能導(dǎo)致死鎖。例如,線程 A 持有鎖 1,等待鎖 2;線程 B 持有鎖 2,等待鎖 1,從而形成了循環(huán)等待的狀態(tài)。
  4. 鎖的嵌套:當(dāng)一個(gè)線程在持有一個(gè)鎖的同時(shí),試圖獲取另一個(gè)鎖時(shí),可能會(huì)導(dǎo)致死鎖。例如,線程 A 持有鎖 1,試圖獲取鎖 2,但鎖 2 已經(jīng)被線程 B 持有,同時(shí)線程 B 正在等待鎖 1,從而導(dǎo)致死鎖。

以下是一些常用的方法,可用于避免死鎖的產(chǎn)生:

  1. 避免使用多個(gè)鎖:如果可能的話,盡量減少對(duì)多個(gè)鎖的使用,從而減少死鎖的可能性。可以考慮使用更細(xì)粒度的鎖,或者使用一種更加高效的并發(fā)編程模型,例如使用無鎖數(shù)據(jù)結(jié)構(gòu)或并發(fā)容器。
  2. 統(tǒng)一獲取鎖的順序:在多個(gè)線程需要獲取多個(gè)鎖的情況下,可以規(guī)定一定的獲取鎖的順序,從而避免循環(huán)等待。例如,如果線程 A 需要獲取鎖 A 和鎖 B,而線程 B 需要獲取鎖 B 和鎖 A,那么可以規(guī)定所有線程在獲取鎖時(shí)都按照相同的順序,例如先獲取鎖 A,再獲取鎖 B,從而避免死鎖的產(chǎn)生。
  3. 設(shè)置鎖的超時(shí)時(shí)間:在獲取鎖時(shí)可以設(shè)置超時(shí)時(shí)間,如果在超時(shí)時(shí)間內(nèi)沒有成功獲取鎖,則放棄鎖資源,從而避免長(zhǎng)時(shí)間的等待導(dǎo)致死鎖。
  4. 避免嵌套鎖:盡量避免在一個(gè)鎖內(nèi)部獲取另一個(gè)鎖,從而避免鎖的嵌套,降低死鎖的可能性。
  5. 及時(shí)釋放鎖:在使用完鎖資源后,盡早釋放鎖,不要持有鎖的時(shí)間過長(zhǎng),從而減少死鎖的風(fēng)險(xiǎn)。
  6. 使用可重入鎖:可重入鎖允許同一個(gè)線程多次獲取同一個(gè)鎖而不會(huì)發(fā)生死鎖,因此在可能的情況下可以考慮使用可重入鎖,避免死鎖的發(fā)生。
  7. 良好的設(shè)計(jì)和資源管理:在編寫多線程并發(fā)程序時(shí),良好的設(shè)計(jì)和資源管理也能夠幫助避免死鎖的產(chǎn)生。例如,合理規(guī)劃資源的分配和釋放,避免資源的長(zhǎng)時(shí)間占有等。

總之,在編寫多線程程序時(shí),需要特別注意鎖的使用、資源的管理和同步協(xié)調(diào),以避免死鎖的發(fā)生。

總結(jié)

了解Java中鎖的分類可以幫助我們?cè)陂_發(fā)中選擇合適的鎖機(jī)制,提高代碼效率和并發(fā)性能,比如:

  • 根據(jù)應(yīng)用場(chǎng)景選擇合適的鎖類型,例如在讀多寫少的情況下可以使用讀寫鎖提高并發(fā)性能;在需要對(duì)共享資源進(jìn)行加鎖保護(hù)的情況下,可以使用互斥鎖防止多個(gè)線程同時(shí)訪問和修改。
  • 根據(jù)鎖特性選用合適的鎖策略,例如在資源競(jìng)爭(zhēng)非常激烈的情況下,公平鎖能夠更好地避免線程饑餓的問題;在需要支持可重入性的場(chǎng)景下,可重入鎖則能夠更便捷地實(shí)現(xiàn)該功能。
  • 通過了解鎖機(jī)制的實(shí)現(xiàn)原理,可以更好地調(diào)優(yōu)程序,例如通過自旋鎖等方式減少線程上下文切換的開銷,從而提高程序效率。

總之,了解Java中鎖的分類是開發(fā)高性能、高可靠性并發(fā)程序的基本要求,對(duì)于提升程序質(zhì)量和運(yùn)行效率具有重要意義。

到此這篇關(guān)于詳細(xì)盤點(diǎn)Java中鎖的分類的文章就介紹到這了,更多相關(guān)Java鎖的分類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論