Java多線程-4-LOCK

四、LOCK

1、鎖的4種狀態

【1】鎖的優化

JDK6爲了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”

從而使得鎖有了4種狀態,並隨着鎖競爭的情況而升級。鎖可以升級但不能降級,鎖的升級被稱爲“鎖膨脹”

【2】偏向鎖狀態

大多數情況下,鎖不僅不存在多線程競爭,而且還總是由同一個線程多次獲得

爲了提升這種情況的性能,從而引入偏向鎖

在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,當一個線程進入和退出同步塊時,不需要進行CAS操作來加鎖和解鎖,直接判斷對象頭的Mark Word裏是否存有指向當前線程的偏向鎖即可

如果存在表示線程已經獲取了鎖,如果不存在則鎖膨脹成輕量級鎖

偏向鎖在JDK6和JDK7中是默認開啓的,但是需要在程序啓動幾秒鐘後纔會被激活

如果需要關閉這個延遲,使用JVM參數

-XX:BiasedLockingStartupDelay=0

如果關閉偏向鎖,使用JVM參數

-XX:-UseBiasedLocking

【3】輕量級鎖狀態

加鎖:JVM在當前線程的棧幀中創建用於存儲鎖標記的空間,並將對象頭中的Displaced Mark Word複製到當中。如果成功表示當前線程獲取鎖成功,如果失敗表示存在鎖競爭,當前線程會嘗試使用自旋來獲取鎖

解鎖:使用原子的CAS操作,將Displaced Mark Word替換回對象頭。如果成功表示沒有鎖競爭發生,如果失敗表示有鎖競爭發生,會導致鎖膨脹成重量級鎖

【4】對比

鎖類型 優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外等待,
和無鎖狀態執行方法相比存在納秒級(1納秒 = 1000 * 1000毫秒)的差別
如果存在鎖競爭,
會帶來額外的撤銷鎖的消耗
只有一個線程訪問同步塊
輕量級鎖 競爭的線程不會阻塞,
提高了程序的響應速度
如果線程一直獲取不到鎖,
使用自旋會消耗CPU
追求響應速度,
同步塊執行速度非常快
重量級鎖 線程競爭不會使用自旋,不會消耗CPU 競爭的線程會阻塞,
會降低響應速度
追求吞吐量,同步塊執行時間較長

2、公平鎖和非公平鎖

公平鎖:每個線程在獲取鎖時,會先檢查該鎖維護的等待隊列,如果隊列爲空,或者當前線程是隊列當中的第一個(head),則該線程獲取鎖。以後會按照FIFO的原則從等待隊列中取到自己(排隊等待獲取鎖)

非公平鎖:每個線程都可以嘗試獲取鎖

synchronized是非公平鎖

java.util.concurrent.locks.ReentrantLock,通過無參構造函數創建的是非公平鎖。通過有參構造函數,且傳入true,則該鎖是一個公平鎖

3、可重入鎖

可重入鎖又稱之爲遞歸鎖,指的是已經獲取鎖的線程,當執行同步方法內的同步方法時,無需再次獲取鎖,即可直接執行方法

synchronized 和 java.util.concurrent.locks.ReentrantLock 都是可重入鎖

4、自旋鎖

線程通過自旋的方式,嘗試獲取鎖,以減少線程上下文切換的開銷

class CasLock {
    private final AtomicReference<Thread> reference = new AtomicReference<Thread>();

    public void lock() {
        for (;;) {
            if (tryAcquire()) {
                break;
            }
        }
    }

    public void unlock() {
        for (;;) {
            if (tryRelease()) {
                break;
            }
        }
    }

    private boolean tryAcquire() {
        Thread thread = Thread.currentThread();
        return reference.compareAndSet(null, thread);
    }

    private boolean tryRelease() {
        Thread thread = Thread.currentThread();
        return reference.compareAndSet(thread, null);
    }

}

5、共享鎖和排他鎖

共享鎖:多個線程都可以獲取該鎖

排他鎖:鎖只能由一個線程所獨享

java.util.concurrent.locks.ReentrantReadWriteLock的讀鎖是線程共享的,而寫鎖是線程獨享的

synchronized 和 java.util.concurrent.locks.ReentrantLock 都是排他鎖

6、讀寫鎖

java.util.concurrent.locks.ReentrantReadWriteLock

讀寫鎖,讀鎖和寫鎖共同存在,讀鎖共享,寫鎖獨享

7、線程兼容和線程對立

線程兼容:要操作的對象本身是線程不安全的,但是可以在操作對象時,通過各種線程安全的手段,來保證在多線程的環境下,操作對象是線程安全的

線程對立:要操作的對象本身是線程安全的,但是由於在多線程的環境下操作對象時,多個線程的併發請求不是線程安全的,從而導致操作對象本身變成了線程不安全的

Java中線程對立的例子:Thread類的廢棄方法(@Deprecated) suspendresume 可能導致死鎖

發佈了155 篇原創文章 · 獲贊 89 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章