1. 程式人生 > >Java的鎖—徹底理解重入鎖(ReentrantLock)

Java的鎖—徹底理解重入鎖(ReentrantLock)

重入鎖簡單理解就是對同一個執行緒而言,它可以重複的獲取鎖。例如這個執行緒可以連續獲取兩次鎖,但是釋放鎖的次數也一定要是兩次。下面是一個簡單例子:

public class ReenterLock {

    private static ReentrantLock lock = new ReentrantLock();

    private static int i = 0;

    // 迴圈1000000次
    private static Runnable runnable = () -> IntStream.range(0, 1000000).forEach((j) -> {
        lock.lock();
        try
{ i++; } finally { lock.unlock(); } }); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); // 利用join,等thread1,thread2結束後,main執行緒才繼續執行,並列印 i
thread1.join(); thread2.join(); // 利用lock保護的 i,最終結果為 2000000,如果不加,則值肯定小於此數值 System.out.println(i); } }

從上面的程式碼可以看到,相比於synchronized,開發者必須手動指定鎖的位置和什麼時候釋放鎖,這樣必然增加了靈活性。

執行緒中斷響應

如果執行緒阻塞於synchronized,那麼要麼獲取到鎖,繼續執行,要麼一直等待。重入鎖提供了另一種可能,就是中斷執行緒。下面的例子是利用兩個執行緒構建一個死鎖,然後中斷其中一個執行緒,使另一個執行緒獲取鎖的例子:

public class ReenterLockInterrupt {
    private static ReentrantLock lock = new ReentrantLock();

    private static Runnable runnable = () -> {
        try {
            // 利用 lockInterruptibly 申請鎖,這是可以進中斷申請的申請鎖操作
            lock.lockInterruptibly();
            // 睡眠20秒,在睡眠結束之前,main方法裡要中斷thread2的獲取鎖操作
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            String threadName = Thread.currentThread().getName();
            // 中斷後丟擲異常,最後要釋放鎖
            // 如果是執行緒1則釋放鎖,因為執行緒2就沒拿到鎖,所以不用釋放
            if ("Thread-1".equals(threadName)) lock.unlock();
            System.out.println(threadName+" 停止");
        }
    };

    public static void main(String[] args) {
        Thread thread1 = new Thread(runnable, "thread-1");
        Thread thread2 = new Thread(runnable, "thread-2");
        thread1.start();

        // 讓主執行緒停一下,讓thread1獲取鎖後再啟動thread2
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 這裡什麼也不做
        }

        thread2.start();
        thread2.interrupt();
    }
}

thread-1拿到鎖之後,執行緒即持有鎖並等待20秒,然後thread-2啟動,並沒有拿到鎖,這時候中斷thread-2執行緒,執行緒2退出。

有限時間的等待鎖

顧名思義,簡單理解就是在指定的時間內如果拿不到鎖,則不再等待鎖。當持有鎖的執行緒出問題導致長時間持有鎖的時候,你不可能讓其他執行緒永遠等待其釋放鎖。下面是一個例子:

public class ReenterTryLock {
    private static ReentrantLock reenterLock = new ReentrantLock();

    private static Runnable runnable = () -> {
        try {
            // tryLock()方法會返回一個布林值,獲取鎖成功則為true
            if (reenterLock.tryLock(3, TimeUnit.SECONDS)) {
                Thread.sleep(5000);
            } else {
                System.out.println(Thread.currentThread().getName() + "獲取鎖失敗");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 最後,如果當前前程在持有鎖,則釋放鎖
            if (reenterLock.isHeldByCurrentThread()) {
                System.out.println(Thread.currentThread().getName() + "釋放鎖了");
                reenterLock.unlock();
            }
        }
    };

    public static void main(String[] args) {
        Thread thread1 = new Thread(runnable, "thread-1");
        Thread thread2 = new Thread(runnable, "thread-2");

        thread1.start();
        thread2.start();
    }
}

這裡使用tryLock()第一個獲取鎖的執行緒,會停止5秒。而獲取鎖的設定為3秒獲取不到鎖則放棄,所以第二個去嘗試獲取鎖的執行緒是獲取不到鎖而被迫停止的。如果tryLock()方法不傳入任何引數,那麼獲取鎖的執行緒不會等待鎖,則立即返回false。

公平鎖與非公平鎖

當一個執行緒釋放鎖時,其他等待的執行緒則有機會獲取鎖,如果是公平鎖,則分先來後到的獲取鎖,如果是非公平鎖則誰搶到鎖算誰的,這就相當於排隊買東西和不排隊買東西是一個道理。Java的synchronized關鍵字就是非公平鎖

那麼重入鎖ReentrantLock()是公平鎖還是非公平鎖?

重入鎖ReentrantLock()是可以設定公平性的,可以參考其構造方法:

// 通過傳入一個布林值來設定公平鎖,為true則是公平鎖,false則為非公平鎖
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

構建一個公平鎖需要維護一個有序佇列,如果實際需求用不到公平鎖則不需要使用公平鎖。下面用一個例子來演示公平鎖與非公平鎖的區別:

public class ReenterTryLockFair {
    // 分別設定公平鎖和非公平鎖,分析列印結果
    private static ReentrantLock lock = new ReentrantLock(true);

    private static Runnable runnable = () -> {
        while (true) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " 獲取了鎖");
            } finally {
                lock.unlock();
            }
        }
    };

    public static void main(String[] args) {
        Thread thread1 = new Thread(runnable, "thread---1");
        Thread thread2 = new Thread(runnable, "thread---2");
        Thread thread3 = new Thread(runnable, "thread---3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

當設定為true即公平鎖的時候,可以看到列印非常規律,擷取一段兒列印結果:

thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖
thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖
thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖
thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖
thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖
thread---1 獲取了鎖
thread---2 獲取了鎖
thread---3 獲取了鎖

可以看到,都是thread–1,thread–2,thread–3,無限迴圈下去,如果設定的為非公平鎖,列印結果就混亂沒有規律了:

thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---3 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---2 獲取了鎖
thread---1 獲取了鎖

Condition

同jdk中的等待/通知機制類似,只不過Condition是用在重入鎖這裡的。有了Condition,執行緒就可以在合適的時間等待,在合適的時間繼續執行。

Condition介面包含以下方法:

// 讓當前執行緒等待,並釋放鎖
void await() throws InterruptedException;
// 和await類似,但在等待過程中不會相應中斷
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
// 喚醒等待中的執行緒
void signal();
// 喚醒等待中的所有執行緒
void signalAll();

下面是一個簡單示例:

public class ReenterLockCondition {
    private static ReentrantLock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();

    private static Runnable runnable = () -> {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "進入等待。。");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "繼續執行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(runnable, "thread--1");
        thread.start();

        Thread.sleep(2000);

        lock.lock();
        condition.signal();
        System.out.println("主執行緒發出訊號");
        lock.unlock();
    }
}

thread–1啟動,拿到鎖,然後進入等待並且釋放鎖,2秒後,主執行緒拿到鎖,然後發出訊號並釋放鎖,最後,thread–1繼續執行。下面是列印結果:

thread--1進入等待。。
主執行緒發出訊號
thread--1繼續執行

相關推薦

Java徹底理解(ReentrantLock)

重入鎖簡單理解就是對同一個執行緒而言,它可以重複的獲取鎖。例如這個執行緒可以連續獲取兩次鎖,但是釋放鎖的次數也一定要是兩次。下面是一個簡單例子: public class ReenterLock { private static Reentran

java基礎總結(二十九)--Java不可和可理解

來自:https://blog.csdn.net/u012545728/article/details/80843595   最近正在閱讀Java ReentrantLock原始碼,始終對可重入和不可重入概念理解不透徹,進行學習後記錄在這裡。 基礎知識 Java多執行緒的wai

JAVA多執行緒 和讀寫

在java多執行緒中,我們真的可以使用synchronized關鍵字來實現執行緒間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個“同步互斥”工作,他就是Lock物件,重入鎖和讀寫鎖。他們具有比synchronized更為強大的功能,並且有嗅探鎖定、多路分支等功能。 一、重入鎖

Java 中15種的介紹:公平,可,獨享,互斥,樂觀,分段,自旋等等

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀寫鎖 樂觀鎖 / 悲觀鎖 分段鎖

JAVA機制-可,可中斷,公平,讀寫,自旋

部落格引用處(以下內容在原有部落格基礎上進行補充或更改,謝謝這些大牛的部落格指導): JAVA鎖機制-可重入鎖,可中斷鎖,公平鎖,讀寫鎖,自旋鎖 在併發程式設計中,經常遇到多個執行緒訪問同一個 共享資源 ,這時候作為開發者必須考慮如何維護資料一致性,在java中synchronized

Java synchronized之“可

概念 可重入鎖:自己可以再次獲取自己的內部的鎖。比如有執行緒A獲得了某物件的鎖,此時這個時候鎖還沒有釋放,當其再次想獲取這個物件的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。 可重入鎖

Java 種15種的介紹:公平,可,獨享,互斥等等...

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨享鎖 / 共享鎖 互斥鎖 / 讀寫鎖 樂觀鎖 / 悲觀鎖 分段鎖 偏向

Java多執行緒--的實現原理

protectedfinalboolean tryRelease(int releases) {               int c = getState() - releases;               if (Thread.currentThread() != getExclusiveO

Java 種15種的介紹:公平,可,獨享,互斥等等

Java 中15種鎖的介紹 在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下

Lock、、寫入

lee 但是 sleep rac ued finally println ava one ReentrantLock 重入鎖 類似於synchronize 區別與寫法上,在需要進行同步的代碼部分加上鎖定,但不要忘記最後一定要釋放鎖定, 不然會造成鎖永遠無法釋放,其他線程

10-Synchronized:悲觀,可

Synchronized:悲觀鎖,可重入鎖 特點:可重入的鎖 可重入鎖,一個獲得的鎖的執行緒沒執行完可以繼續獲得該鎖。 執行緒佔用鎖的時候,如果執行的同步程式碼出現異常,會自動將鎖讓出。 同步程式碼塊的程式碼是同步執行的(一次執行完),而非同步程

舉例講解 Python 中的死、可和互斥

簡單來說,死鎖是一個資源被多次呼叫,而多次呼叫方都未能釋放該資源就會造成死鎖,這裡結合例子說明下兩種常見的死鎖情況。 1、迭代死鎖 該情況是一個執行緒“迭代”請求同一個資源,直接就會造成死鎖: Python import

--自旋、阻塞、可、悲觀、樂觀、讀寫、偏向所、輕量級、重量級膨脹、物件和類

參考:http://blog.csdn.net/a314773862/article/details/54095819 自旋鎖 自旋鎖可以使執行緒在沒有取得鎖的時候,不被掛起,而轉去執行一個空迴圈,(即所謂的自旋,就是自己執行空迴圈),若在若干個空迴圈後,執行緒如果可以獲得

和不可

鎖的簡單應用用lock來保證原子性(this.count++這段程式碼稱為臨界區)什麼是原子性,就是不可分,從頭執行到尾,不能被其他執行緒同時執行。可通過CAS來實現原子操作CAS(Compare and Swap):CAS操作需要輸入兩個數值,一箇舊值(期望操作前的值)和一個新值,在操作期間先比較下舊值有沒

不可、可的實現以及測試

可重入鎖定義:執行緒去請求自己擁有的鎖可請求到interface SelfDefineLock{ void lock(); void unlock(); } class Father{ SelfDefineLock lock; Father(SelfDefineLo

python多執行緒程式設計(4): 死和可

線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面看一個死鎖的例子: # encoding: UTF-8import threadingimport timec

:可 可中斷 公平 讀寫

轉自:http://www.cnblogs.com/wihainan/p/4762800.html 侵刪 1.可重入鎖 如果鎖具備可重入性,則稱作為可重入鎖。 像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機

【分散式】06-Zookeeper實現分散式:可原始碼分析

前言 前面已經講解了Redis的客戶端Redission是怎麼實現分散式鎖的,大多都深入到原始碼級別。 在分散式系統中,常見的分散式鎖實現方案還有Zookeeper,接下來會深入研究Zookeeper是如何來實現分散式鎖的。 Zookeeper初識 檔案系統 Zookeeper維護一個類似檔案系統的資料結構

JavaReentrantlock)原理,公平與非公平

一個 star spa void 模板 ont thread fin 模式 1、特點: 已獲取鎖的線程再次請求鎖,可以直接獲取。 2、實現: 自定義內部類 Sync,繼承 AbstarctQueuedSynchronizer : 2.1、獲取鎖:lock() a、公平鎖:

Java並發(九): ReentrantLock

阻塞隊列 輪詢 cee condition 源碼分析 interrupt 同步 iter 投票 一、ReentrantLock類結構 public class ReentrantLock implements Lock, java.io.Serializable {