淺談Java中的公平鎖和非公平鎖,可重入鎖,自旋鎖


公平鎖和非公平鎖

這里主要體現在ReentrantLock這個類里面了 公平鎖、非公平鎖的創建方式:

 

//創建一個非公平鎖,默認是非公平鎖

Lock lock = new ReentrantLock();

Lock lock = new ReentrantLock(false);

//創建一個公平鎖,構造傳參true

Lock lock = new ReentrantLock(true);

相關源碼:

    public ReentrantLock() {

        sync = new NonfairSync();

    }

    public ReentrantLock(boolean fair) {

        sync = fair ? new FairSync() : new NonfairSync();

    }

————————————————

公平鎖是指多個線程按照申請鎖的順序來獲取鎖,線程直接進入隊列中排隊,隊列中的第一個線程才能獲得鎖。公平鎖的優點是等待鎖的線程不會餓死。缺點是整體吞吐效率相對非公平鎖要低,等待隊列中除第一個線程以外的所有線程都會阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大.

對於非公平鎖,管理員對打水的人沒有要求。即使等待隊伍里有排隊等待的人,但如果在上一個人剛打完水把鎖還給管理員而且管理員還沒有允許等待隊伍里下一個人去打水時,剛好來了一個插隊的人,這個插隊的人是可以直接從管理員那里拿到鎖去打水,不需要排隊,原本排隊等待的人只能繼續等待。

 

通過上圖中的源代碼對比,我們可以明顯的看出公平鎖與非公平鎖的lock()方法唯一的區別就在於公平鎖在獲取同步狀態時多了一個限制條件:hasQueuedPredecessors()。

再進入hasQueuedPredecessors(),可以看到該方法主要做一件事情:主要是判斷當前線程是否位於同步隊列中的第一個。如果是則返回true,否則返回false。

綜上,公平鎖就是通過同步隊列來實現多個線程按照申請鎖的順序來獲取鎖,從而實現公平的特性。非公平鎖加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,所以存在后申請卻先獲得鎖的情況

可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,再進入該線程的內層方法會自動獲取鎖,Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個優點是可一定程度避免死鎖

先寫個demo大致的看一下:

public class AtomicDemo {

    public static void main(String[] args) {

        person person = new person();
        for (int i = 0; i < 10; i++) {
            new Thread(){
                @Override
                public void run() {
                    person.aaa();
                }
            }.start();
        }
    }
}
class person{

    public synchronized void aaa(){
        System.out.println(Thread.currentThread().getName()+"aaa");
        this.bbb();
    }
    public synchronized void bbb(){
        System.out.println(Thread.currentThread().getName()+"bbb");
    }
}

結果:

Thread-1aaa

Thread-1bbb

Thread-2aaa

Thread-2bbb

Thread-0aaa

Thread-0bbb

Thread-3aaa

Thread-3bbb

Thread-4aaa

Thread-4bbb

Thread-5aaa

Thread-5bbb

Thread-7aaa

Thread-7bbb

Thread-6aaa

Thread-6bbb

Thread-9aaa

Thread-9bbb

Thread-8aaa

Thread-8bbb

說明:

在上面的代碼中,類中的兩個方法都是被內置鎖synchronized修飾的,aaa()方法中調用bbb()方法。因為內置鎖是可重入的,所以同一個線程在調用bbb()時可以直接獲得當前對象的鎖,進入bbb()進行操作。

如果是一個不可重入鎖,那么當前線程在調用bbb()之前需要將執行aaa()時獲取當前對象的鎖釋放掉,實際上該對象鎖已被當前線程所持有,且無法釋放。所以此時會出現死鎖。

 

自旋鎖

先說說概念

阻塞或喚醒一個Java線程需要操作系統切換CPU狀態來完成,這種狀態轉換需要耗費處理器時間。如果同步代碼塊中的內容過於簡單,狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長。

在許多場景中,同步資源的鎖定時間很短,為了這一小段時間去切換線程,線程掛起和恢復現場的花費可能會讓系統得不償失。如果物理機器有多個處理器,能夠讓兩個或以上的線程同時並行執行,我們就可以讓后面那個請求鎖的線程不放棄CPU的執行時間,看看持有鎖的線程是否很快就會釋放鎖。

而為了讓當前線程“稍等一下”,我們需讓當前線程進行自旋,如果在自旋完成后前面鎖定同步資源的線程已經釋放了鎖,那么當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。

自旋鎖本身是有缺點的,它不能代替阻塞。自旋等待雖然避免了線程切換的開銷,但它要占用處理器時間。如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那么自旋的線程只會白浪費處理器資源。所以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(默認是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當掛起線程。

自旋鎖的實現原理同樣也是CAS,AtomicInteger中調用unsafe進行自增操作的源碼中的do-while循環就是一個自旋操作,如果修改數值失敗則通過循環來執行自旋,直至修改成功。

看看源碼中的CAS方法:

 

簡單的說一下參數:

Var1---->this(也就是當前的對象)

Var2---->當前對象的主內存地址

Var4---->要加的值

Var5---->自旋完成后返回的值

再說細一點:

var5 = this.getIntVolatile(var1, var2);

這個方法的就是在內存中找到var1對象的內存地址為var2的值賦值給var5

再去判斷:

this.compareAndSwapInt(var1, var2, var5, var5 + var4)

當前var5的值與獲取到主內存中的值是不是一樣的,一樣的就進行var4+var5的操作,返回true,否則就不做操作

 

隨便寫了一下,先到這里,后面再對其他的鎖進行總結,喜歡的支持一下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM