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

詳細盤點Java中鎖的分類

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

什么是鎖

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

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

隱式鎖和顯式鎖

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

隱式鎖

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

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

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

優(yōu)點:

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

缺點:

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

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

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

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

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

優(yōu)點:

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

缺點:

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

悲觀鎖和樂觀鎖

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

悲觀鎖

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

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

悲觀鎖存的問題

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

樂觀鎖

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

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

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

樂觀鎖存在的問題

CAS雖然很?效的解決原?操作,但是CAS仍然存在三?問題:ABA問題自旋時間過長只能保證單個變量的原子性。

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

公平鎖和非公平鎖

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

公平鎖

公平鎖(Fair Lock)是指當(dāng)多個線程競爭鎖時,先到先得,后到后等,按照請求的先后順序來獲取鎖。這樣可以避免某些線程長時間等待鎖,從而提高系統(tǒng)的公平性。但是,公平鎖也有一些缺點,比如性能開銷較大,因為需要維護一個有序的等待隊列,并且可能導(dǎo)致更多的上下文切換。

優(yōu)點:

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

缺點:

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

非公平鎖

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

優(yōu)點:

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

缺點:

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

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

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

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

下面是一段ReentrantLock公平鎖的簡單實現(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();
    }
}

運行結(jié)果

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

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

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

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

可重入鎖和非可重入鎖

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

可重入鎖

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

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

在線程持有鎖的情況下,再次請求該鎖時,如果鎖是可重入的,線程會成功獲取鎖而不會被阻塞。

常見的可重入鎖有:

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

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

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

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

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

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

優(yōu)點:

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

缺點:

  1. 復(fù)雜性高:相比于內(nèi)置鎖 synchronized 關(guān)鍵字,可重入鎖的使用復(fù)雜度較高,需要手動加鎖和解鎖,容易出錯。
  2. 性能相對較低:相較于內(nèi)置鎖 synchronized 關(guān)鍵字,可重入鎖的性能可能較低,因為它提供了更多的功能和靈活性。

非可重入鎖

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

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

缺點:

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

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

非可重入鎖在 Java 中沒有現(xiàn)成的實現(xiàn)。非可重入鎖通常是通過自定義的鎖機制來實現(xiàn)的,但在 Java 標(biāo)準庫中并沒有提供非可重入鎖的實現(xiàn)。需要注意的是,使用非可重入鎖時需要非常小心,因為它可能導(dǎo)致死鎖或其他并發(fā)問題。

獨占鎖和共享鎖

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

獨占鎖

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

獨占鎖的實現(xiàn):

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

優(yōu)點:

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

缺點:

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

共享鎖

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

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

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

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

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

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

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

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

優(yōu)點:

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

缺點:

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

以下是一個共享鎖的代碼示例:

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 {
                // 寫操作,這里簡單地用數(shù)組來模擬
                data[0] = "writer1 data";
                Thread.sleep(1000); // 模擬寫操作需要一定時間
            } 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 {
                // 讀操作,這里簡單地輸出數(shù)組的第一個元素
                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();
            }
        });
        // 啟動寫線程
        writer1.start();
        writer2.start();
        // 啟動讀線程
        reader1.start();
        reader2.start();
        // 等待所有線程結(jié)束
        writer1.join();
        writer2.join();
        reader1.join();
        reader2.join();
    }
}

運行結(jié)果

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

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

偏向鎖、輕量級鎖和重量級鎖

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

偏向鎖

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

偏向鎖優(yōu)點:

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

偏向鎖缺點:

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

輕量級鎖

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

輕量級鎖優(yōu)點:

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

輕量級鎖缺點:

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

重量級鎖

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

重量級鎖優(yōu)點:

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

重量級鎖缺點:

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

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

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

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

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

分段鎖

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

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

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

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

下面是一個使用ConcurrentHashMap實現(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();
            }
        }
    }
}

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

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

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

分段鎖的缺點:

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

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

自旋鎖

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

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

下面是一個簡單的自旋鎖代碼示例,使用Java中的AtomicBoolean實現(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() 方法實現(xiàn)了自旋。在 lock() 方法中,如果當(dāng)前鎖已經(jīng)被占用,那么就會一直自旋,直到獲得鎖為止;在 unlock() 方法中,我們將鎖狀態(tài)設(shè)置為false,表示釋放鎖。

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

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

自旋鎖的缺點是:

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

死鎖

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

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

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

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

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

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

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

總結(jié)

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

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

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

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

相關(guān)文章

  • Java虛擬機運行時數(shù)據(jù)區(qū)域匯總

    Java虛擬機運行時數(shù)據(jù)區(qū)域匯總

    這篇文章主要給大家介紹了關(guān)于Java虛擬機運行時數(shù)據(jù)區(qū)域的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • SpringBoot Jpa 自定義查詢實現(xiàn)代碼詳解

    SpringBoot Jpa 自定義查詢實現(xiàn)代碼詳解

    這篇文章主要介紹了SpringBoot Jpa 自定義查詢實現(xiàn)代碼詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • MyBatis-Plus中使用EntityWrappe進行列表數(shù)據(jù)倒序設(shè)置方式

    MyBatis-Plus中使用EntityWrappe進行列表數(shù)據(jù)倒序設(shè)置方式

    這篇文章主要介紹了MyBatis-Plus中使用EntityWrappe進行列表數(shù)據(jù)倒序設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 基于Spring中的事務(wù)@Transactional細節(jié)與易錯點、幻讀

    基于Spring中的事務(wù)@Transactional細節(jié)與易錯點、幻讀

    這篇文章主要介紹了基于Spring中的事務(wù)@Transactional細節(jié)與易錯點、幻讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 如何在Spring中使用編碼方式動態(tài)配置Bean詳解

    如何在Spring中使用編碼方式動態(tài)配置Bean詳解

    這篇文章主要給大家介紹了關(guān)于如何在Spring中使用編碼方式動態(tài)配置Bean的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-05-05
  • java防盜鏈在報表中的應(yīng)用實例(推薦)

    java防盜鏈在報表中的應(yīng)用實例(推薦)

    下面小編就為大家?guī)硪黄猨ava防盜鏈在報表中的應(yīng)用實例(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • SpringMVC+MyBatis分頁(最新)

    SpringMVC+MyBatis分頁(最新)

    本篇文章主要介紹了SpringMVC+MyBatis分頁,具有一定的參考價值,有興趣的可以了解一下。
    2016-12-12
  • 跟我學(xué)Java Swing之游戲設(shè)計(2)

    跟我學(xué)Java Swing之游戲設(shè)計(2)

    跟我學(xué)Java Swing之游戲設(shè)計(2)...
    2006-12-12
  • 基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù)

    基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù)

    這篇文章主要介紹了基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-11-11
  • Java Enum的簡單使用

    Java Enum的簡單使用

    這篇文章主要為大家詳細介紹了Java Enum的簡單使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09

最新評論