【Java】深入理解ReentrantLock可重入鎖之簡單使用

珍心發表於2020-10-22

學而不思則罔,思而不學則殆


ReentrantLock簡介

ReentrantLock主要利用CAS+AQS佇列來實現。它支援公平鎖和非公平鎖,兩者的實現類似。

CAS:Compare and Swap,比較並交換。CAS有3個運算元:記憶體值V、預期值A、要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。該操作是一個原子操作,被廣泛的應用在Java的底層實現中。在Java中,CAS主要是由sun.misc.Unsafe這個類通過JNI呼叫CPU底層指令實現

ReentrantLock方法總結

方法說明
lock等待執行緒獲取鎖,不能被中斷
lockInterruptibly等待執行緒獲取鎖,接收中斷訊號
tryLock方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,這個方法無論如何都會立即返回
tryLock(long timeout, TimeUnit unit)嘗試獲取鎖,在指定時間內獲取成功會返回,超過時間返回false,可以被中斷
getHoldCount獲取當前執行緒持有鎖的次數
getQueueLength獲取等待鎖的執行緒個數
isFair鎖是否公平
hasQueuedThreads查詢是否有執行緒正在等待獲取此鎖
hasQueuedThread(Thread)查詢是指定執行緒是否正在等待獲取此鎖
isHeldByCurrentThread查詢當前執行緒是否持有此鎖
isLocked查詢此鎖是否由任何執行緒持有
newCondition建立Condition

Condition方法總結

Condition可以定向喚醒執行緒

方法說明
await等待某個條件喚醒或者中斷
await (long,TimeUnit)等待xignal訊號或者中斷或者超時
awaitNanos等待signal訊號或者中斷或者超時
awaitUntil使當前執行緒等待,直到收到訊號或中斷,或指定的截止日期過期。
awaitUninterruptibly等待訊號,不能中斷
signal傳送一個訊號
signalAll傳送該條件的所有訊號

ReentrantLock簡單使用

常用方法測試

    private static void testLock() {
        sDiyThread1.start();
        sDiyThread2.start();
        sDiyThread3.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        showMsg();
    }


    static ReentrantLock sReentrantLock = new ReentrantLock();
    static DiyThread sDiyThread1 = new DiyThread(sReentrantLock);
    static DiyThread sDiyThread2 = new DiyThread(sReentrantLock);
    static DiyThread sDiyThread3 = new DiyThread(sReentrantLock);

    static void showMsg() {
        System.out.println(Thread.currentThread() + " 獲取當前執行緒持有鎖的次數:" + sReentrantLock.getHoldCount());
        System.out.println(Thread.currentThread() + " 獲取等待鎖的執行緒個數:" + sReentrantLock.getQueueLength());
        System.out.println(Thread.currentThread() + " 鎖是否公平:" + sReentrantLock.isFair());
        System.out.println(Thread.currentThread() + " 查詢是否有執行緒正在等待獲取此鎖:" + sReentrantLock.hasQueuedThreads());
        System.out.println(Thread.currentThread() + " 查詢是指定執行緒" + sDiyThread1.getName() + "正在等待獲取此鎖:" + sReentrantLock.hasQueuedThread(sDiyThread1));
        System.out.println(Thread.currentThread() + " 查詢是指定執行緒" + sDiyThread2.getName() + "正在等待獲取此鎖:" + sReentrantLock.hasQueuedThread(sDiyThread2));
        System.out.println(Thread.currentThread() + " 查詢是指定執行緒" + sDiyThread3.getName() + "正在等待獲取此鎖:" + sReentrantLock.hasQueuedThread(sDiyThread3));
        System.out.println(Thread.currentThread() + " 查詢當前執行緒是否持有此鎖:" + sReentrantLock.isHeldByCurrentThread());
        System.out.println(Thread.currentThread() + " 查詢此鎖是否由任何執行緒持有:" + sReentrantLock.isLocked());
    }

    static class DiyThread extends Thread {
        ReentrantLock mReentrantLock;

        public DiyThread(ReentrantLock reentrantLock) {
            this.mReentrantLock = reentrantLock;
        }

        @Override
        public void run() {
            try {
                //1.獲得鎖
                mReentrantLock.lock();
                mReentrantLock.lock();
                TimeUnit.SECONDS.sleep(1);
                showMsg();
                //睡眠一分鐘
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //2.釋放重入鎖
                mReentrantLock.unlock();
                mReentrantLock.unlock();
            }
        }
    }

新建三個執行緒,其中一個執行緒獲取鎖後睡眠1分鐘,這個時候列印一下當前的鎖獲取情況資訊:

資訊如下:

Thread[Thread-0,5,main] 獲取當前執行緒持有鎖的次數:2
Thread[Thread-0,5,main] 獲取等待鎖的執行緒個數:2
Thread[Thread-0,5,main] 鎖是否公平:false
Thread[Thread-0,5,main] 查詢是否有執行緒正在等待獲取此鎖:true
Thread[Thread-0,5,main] 查詢是指定執行緒Thread-0正在等待獲取此鎖:false
Thread[Thread-0,5,main] 查詢是指定執行緒Thread-1正在等待獲取此鎖:true
Thread[Thread-0,5,main] 查詢是指定執行緒Thread-2正在等待獲取此鎖:true
Thread[Thread-0,5,main] 查詢當前執行緒是否持有此鎖:true
Thread[Thread-0,5,main] 查詢此鎖是否由任何執行緒持有:true

Thread[main,5,main] 獲取當前執行緒持有鎖的次數:0
Thread[main,5,main] 獲取等待鎖的執行緒個數:2
Thread[main,5,main] 鎖是否公平:false
Thread[main,5,main] 查詢是否有執行緒正在等待獲取此鎖:true
Thread[main,5,main] 查詢是指定執行緒Thread-0正在等待獲取此鎖:false
Thread[main,5,main] 查詢是指定執行緒Thread-1正在等待獲取此鎖:true
Thread[main,5,main] 查詢是指定執行緒Thread-2正在等待獲取此鎖:true
Thread[main,5,main] 查詢當前執行緒是否持有此鎖:false
Thread[main,5,main] 查詢此鎖是否由任何執行緒持有:true

測試tryLock

tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

    static class TestTryLockThread extends Thread {
        ReentrantLock mReentrantLock;

        public TestTryLockThread(ReentrantLock reentrantLock) {
            this.mReentrantLock = reentrantLock;
        }

        @Override
        public void run() {
            //1.嘗試獲得鎖
            System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " tryLock");
            if (mReentrantLock.tryLock()) {
                System.out.println(Thread.currentThread() + "獲取到鎖!");
            } else {
                System.out.println(Thread.currentThread() + "獲取不到鎖!");
                return;
            }
            try {
                boolean isLocked = mReentrantLock.isHeldByCurrentThread();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " isLocked:" + isLocked);
                if (isLocked) {
                    TimeUnit.MINUTES.sleep(1);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //2.釋放重入鎖
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " unlock");
                mReentrantLock.unlock();
            }
        }
    }

測試結果如下:

1603153131202 Thread[Thread-3,5,main] tryLock
Thread[Thread-3,5,main]獲取到鎖!
1603153131203 Thread[Thread-3,5,main] isLocked:true
1603153131203 Thread[Thread-4,5,main] tryLock
Thread[Thread-4,5,main]獲取不到鎖!
1603153131203 Thread[Thread-5,5,main] tryLock
Thread[Thread-5,5,main]獲取不到鎖!
1603153191204 Thread[Thread-3,5,main] unlock

執行緒【Thread-3】獲取到了鎖,然後休眠1分鐘,【Thread-4】【Thread-5】沒有獲取到鎖,但是呢,立馬就返回了。

測試tryLock(long timeout, TimeUnit unit)

這個方法去限定了一個嘗試獲取鎖的時間。
—獲取鎖成功則返回true;
—當失敗是分為兩種情況:
在引數範圍內,則不會立即返回值,會等待一段時間,這個時間就是傳入的具體引數值,在這個時間內獲取鎖成功,則依舊返回true;當過了引數範圍後,還是獲取鎖失敗,則立即返回false。
比如如下的測試demo:

    //指定時間內是否獲取到鎖
    static class TestTryLockTimeThread extends Thread {
        ReentrantLock mReentrantLock;
        int seconds;

        public TestTryLockTimeThread(ReentrantLock reentrantLock, int seconds) {
            this.mReentrantLock = reentrantLock;
            this.seconds = seconds;
        }

        @Override
        public void run() {
            //1.嘗試獲得鎖
            System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " tryLock 嘗試獲取鎖的時間為:" + this.seconds);

            try {
                if (mReentrantLock.tryLock(seconds, TimeUnit.SECONDS)) {
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖!");
                    try {
                        boolean isHeldByCurrentThread = mReentrantLock.isHeldByCurrentThread();
                        System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " isHeldByCurrentThread:" + isHeldByCurrentThread);
                        if (isHeldByCurrentThread) {
                            TimeUnit.SECONDS.sleep(10); //拿到鎖後休眠10s
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //2.釋放重入鎖
                        System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " unlock");
                        mReentrantLock.unlock();
                    }
                } else {
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取不到鎖!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static void testTryLockLT() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        TestTryLockTimeThread timeThread1 = new TestTryLockTimeThread(reentrantLock, 0);
        TestTryLockTimeThread timeThread2 = new TestTryLockTimeThread(reentrantLock, 5); //5秒內沒有拿到鎖就返回
        TestTryLockTimeThread timeThread3 = new TestTryLockTimeThread(reentrantLock, 15);//15秒內沒有拿到鎖就返回
        timeThread1.start();
        TimeUnit.SECONDS.sleep(1); //休眠一秒,確保timeThread1拿到鎖
        timeThread2.start();
        timeThread3.start();
    }

新建三個執行緒獲取重入鎖,【timeThread2】等待5秒還沒有獲取成功就返回,【timeThread3】等待15秒還沒有獲取成功就返回。而其中【timeThread1】會先獲取到鎖後休眠10s,那麼就會導致【timeThread2】獲取失敗,【timeThread3】獲取成功。log資訊如下:

1603154345942 Thread[Thread-3,5,main] tryLock 嘗試獲取鎖的時間為:0
1603154345943 Thread[Thread-3,5,main]獲取到鎖!
1603154345943 Thread[Thread-3,5,main] isHeldByCurrentThread:true
1603154346943 Thread[Thread-4,5,main] tryLock 嘗試獲取鎖的時間為:5
1603154346944 Thread[Thread-5,5,main] tryLock 嘗試獲取鎖的時間為:15
1603154351943 Thread[Thread-4,5,main]獲取不到鎖!
1603154355944 Thread[Thread-3,5,main] unlock
1603154355945 Thread[Thread-5,5,main]獲取到鎖!
1603154355945 Thread[Thread-5,5,main] isHeldByCurrentThread:true
1603154365946 Thread[Thread-5,5,main] unlock

可以看到【Thread-4】等待了5秒,在【46 - 51】的時候,就直接返回了。而【Thread-5】等待15秒,在【1603154355945】的是否獲取到了鎖。

測試lockInterruptibly

獲取鎖的時候可中斷

    static class TestLockInterruptiblyThread extends Thread {
        ReentrantLock mReentrantLock;

        public TestLockInterruptiblyThread(ReentrantLock reentrantLock) {
            this.mReentrantLock = reentrantLock;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lockInterruptibly" );
                //1.嘗試獲得鎖
                mReentrantLock.lockInterruptibly();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖!");
                //獲取鎖後休眠10s
                TimeUnit.SECONDS.sleep(20);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖!");
                    mReentrantLock.unlock();
                }
            }
        }
    }

    private static void testLockInterruptibly() {
        ReentrantLock reentrantLock = new ReentrantLock();
        TestLockInterruptiblyThread thread1 = new TestLockInterruptiblyThread(reentrantLock);
        TestLockInterruptiblyThread thread2 = new TestLockInterruptiblyThread(reentrantLock);
        TestLockInterruptiblyThread thread3 = new TestLockInterruptiblyThread(reentrantLock);
        thread1.setName("zy-test1");
        thread2.setName("zy-test2");
        thread3.setName("zy-test3");
        thread1.start();
        try {
            //休眠5s確保thread1獲取鎖
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread3.start();
        try {
            //休眠10s
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //傳送中斷訊號
        System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " thread2 thread3 interrupt");
        thread2.interrupt();
        thread3.interrupt();
    }

測試主要是新建三個執行緒:

第一個執行緒獲取鎖休眠20s
第二個執行緒和第三個執行緒5秒後啟動,確保第一個執行緒獲取鎖
在這10秒中通過jstack列印一下執行緒狀態資訊
啟動10秒後傳送中斷訊號,第二個和第三個執行緒

log資訊如下:

1603237475627 Thread[zy-test1,5,main] lockInterruptibly
1603237475627 Thread[zy-test1,5,main]獲取到鎖!
1603237480628 Thread[zy-test2,5,main] lockInterruptibly
1603237480628 Thread[zy-test3,5,main] lockInterruptibly
1603237490628 Thread[main,5,main] thread2 thread3 interrupt
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.lock.ReentrantLockTest$TestLockInterruptiblyThread.run(ReentrantLockTest.java:38)
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.lock.ReentrantLockTest$TestLockInterruptiblyThread.run(ReentrantLockTest.java:38)
1603237495627 Thread[zy-test1,5,main]釋放鎖!

可以看到執行緒【zy-test1】正常休眠結束釋放鎖,執行緒【zy-test2】【zy-test3】被中斷了但我們手動傳送中斷訊號的時候,列印出了中斷的異常堆疊。

其中線上程【zy-test2】【zy-test3】等待獲取鎖的執行緒狀態如下:

$ jstack.exe 6056
2020-10-21 07:44:48
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):

"zy-test3" #16 prio=5 os_prio=0 tid=0x0000000022864000 nid=0x5e8 waiting on condition [0x000000002350e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007406ab408> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:897)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at com.lock.ReentrantLockTest$TestLockInterruptiblyThread.run(ReentrantLockTest.java:38)

"zy-test2" #15 prio=5 os_prio=0 tid=0x0000000022860800 nid=0x964 waiting on condition [0x000000002340f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007406ab408> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:897)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at com.lock.ReentrantLockTest$TestLockInterruptiblyThread.run(ReentrantLockTest.java:38)

"zy-test1" #14 prio=5 os_prio=0 tid=0x0000000022803000 nid=0x33d8 waiting on condition [0x000000002330e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.lock.ReentrantLockTest$TestLockInterruptiblyThread.run(ReentrantLockTest.java:41)

此時三個執行緒的狀態分別是:

“zy-test1” TIMED_WAITING (sleeping)
“zy-test2” WAITING (parking)
“zy-test3” WAITING (parking)

知道此時三個執行緒都是出於掛起的狀態,但是掛起的方式不同,【zy-test1】是通過sleep方式,進入TIMED_WAITING 狀態,可以通過中斷訊號打斷或者時間到了自動喚醒。【zy-test2】和【zy-test3】則是通過Unsafe.park方法讓執行緒處於WAITING 狀態。

測試Condition

測試Condition.await()

    static class TestConditionThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestConditionThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "await start");
                mCondition.await();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "await end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }

    //簡單使用
    private static void testNewCondition1() {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        TestConditionThread thread = new TestConditionThread(reentrantLock, condition);
        thread.setName("test_condition_await");
        thread.start();
    }

新建一個執行緒,獲取鎖後呼叫await()進入等待狀態。執行緒狀態如下:

$ jstack.exe 1220
2020-10-21 08:19:52
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):

"DestroyJavaVM" #15 prio=5 os_prio=0 tid=0x00000000025ce800 nid=0x1d20 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"test_condition_await" #14 prio=5 os_prio=0 tid=0x0000000021ef1800 nid=0x3eb4 waiting on condition [0x0000000022a6f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007406ad228> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at com.lock.ReentrantLockTest$TestConditionThread.run(ReentrantLockTest.java:45)

通過中斷喚醒

        TimeUnit.SECONDS.sleep(2);
        System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " interrupt");
        thread.interrupt();
1603239799798 Thread[test_condition_await,5,main] lock
1603239799798 Thread[test_condition_await,5,main]獲取到鎖
1603239799798 Thread[test_condition_await,5,main]await start
1603239801799 Thread[main,5,main] interrupt
1603239801802 Thread[test_condition_await,5,main]釋放鎖
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at com.lock.ReentrantLockTest$TestConditionThread.run(ReentrantLockTest.java:47)

傳送中斷訊號後,執行緒被打斷,喚醒執行緒。

通過signal喚醒

新建一個執行緒用來測試傳送signal訊號

    static class TestSignalThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestSignalThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " 傳送signal訊號");
                mCondition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }
        TimeUnit.SECONDS.sleep(2);
        TestSignalThread signalThread = new TestSignalThread(reentrantLock, condition);
        signalThread.setName("test_signal");
        signalThread.start();

測試結果如下:

1603240076885 Thread[test_condition_await,5,main] lock
1603240076886 Thread[test_condition_await,5,main]獲取到鎖
1603240076886 Thread[test_condition_await,5,main]await start

1603240078883 Thread[test_signal,5,main] lock
1603240078883 Thread[test_signal,5,main]獲取到鎖
1603240078883 Thread[test_signal,5,main] 傳送signal訊號
1603240078883 Thread[test_signal,5,main]釋放鎖

1603240078883 Thread[test_condition_await,5,main]await end
1603240078883 Thread[test_condition_await,5,main]釋放鎖

【test_signal】傳送訊號後且釋放鎖後,【test_condition_await】被喚醒。

通過signalAll喚醒

    static class TestSignalThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestSignalThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " 傳送signalAll訊號");
                mCondition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }
1603240230998 Thread[test_condition_await,5,main] lock
1603240230998 Thread[test_condition_await,5,main]獲取到鎖
1603240230998 Thread[test_condition_await,5,main]await start
1603240233002 Thread[test_signal,5,main] lock
1603240233002 Thread[test_signal,5,main]獲取到鎖
1603240233002 Thread[test_signal,5,main] 傳送signalAll訊號
1603240233002 Thread[test_signal,5,main]釋放鎖
1603240233003 Thread[test_condition_await,5,main]await end
1603240233003 Thread[test_condition_await,5,main]釋放鎖

測試await(long time, TimeUnit unit)

超時等待,超過時間沒有喚醒會自動結束

    static class TestConditionThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestConditionThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "await(10, TimeUnit.SECONDS) start");
                mCondition.await(10, TimeUnit.SECONDS);
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "await(10, TimeUnit.SECONDS) end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }
    private static void testNewCondition1() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        TestConditionThread thread = new TestConditionThread(reentrantLock, condition);
        thread.setName("test_condition_await");
        thread.start();
    }

等待10秒後,沒有收到中斷訊號或者signal訊號也會自動喚醒:

1603241143625 Thread[test_condition_await,5,main]獲取到鎖
1603241143625 Thread[test_condition_await,5,main]await(10, TimeUnit.SECONDS) start
1603241153627 Thread[test_condition_await,5,main]await(10, TimeUnit.SECONDS) end
1603241153627 Thread[test_condition_await,5,main]釋放鎖

測試awaitNanos

    static class TestConditionThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestConditionThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitNanos start");
                long na = TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS);
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " na:"+na);
                mCondition.awaitNanos(na);
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitNanos end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }

測試結果:
等待5秒後,沒有收到中斷訊號或者signal訊號也會自動喚醒:

1603241437769 Thread[test_condition_await,5,main] lock
1603241437770 Thread[test_condition_await,5,main]獲取到鎖
1603241437770 Thread[test_condition_await,5,main]awaitNanos start
1603241437770 Thread[test_condition_await,5,main] na:5000000000
1603241442771 Thread[test_condition_await,5,main]awaitNanos end
1603241442771 Thread[test_condition_await,5,main]釋放鎖

測試awaitUntil

使當前執行緒等待,直到收到訊號或中斷,或指定的截止日期過期。

    static class TestConditionThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestConditionThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitUntil start");
                mCondition.awaitUntil(new Date(System.currentTimeMillis() + 1000 * 5));
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitUntil end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }

測試結果,測試截止日期為當前時間+5秒:

1603283671821 Thread[test_condition_await,5,main] lock
1603283671821 Thread[test_condition_await,5,main]獲取到鎖
1603283671821 Thread[test_condition_await,5,main]awaitUntil start
1603283676832 Thread[test_condition_await,5,main]awaitUntil end
1603283676832 Thread[test_condition_await,5,main]釋放鎖

測試awaitUninterruptibly

導致當前執行緒等待,直到收到訊號。意思中斷不了

    static class TestConditionThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestConditionThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖");
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitUninterruptibly start");
                mCondition.awaitUninterruptibly();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "awaitUninterruptibly end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }
    //測試傳送signal的執行緒
    static class TestSignalThread extends Thread {
        ReentrantLock mReentrantLock;
        Condition mCondition;

        public TestSignalThread(ReentrantLock reentrantLock, Condition condition) {
            this.mReentrantLock = reentrantLock;
            this.mCondition = condition;
        }

        @Override
        public void run() {
            try {
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " lock");
                //1.嘗試獲得鎖
                mReentrantLock.lock();
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "獲取到鎖後休眠2秒");
                TimeUnit.SECONDS.sleep(2);
                System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " 傳送signalAll訊號");
                mCondition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (mReentrantLock.isHeldByCurrentThread()) {
                    //釋放鎖
                    System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + "釋放鎖");
                    mReentrantLock.unlock();
                }
            }
        }
    }

    private static void testNewCondition1() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        TestConditionThread thread = new TestConditionThread(reentrantLock, condition);
        thread.setName("test_condition_await");
        thread.start();

        TimeUnit.SECONDS.sleep(2);
        System.out.println(System.currentTimeMillis() + " " + Thread.currentThread() + " interrupt");
        thread.interrupt();
        TestSignalThread signalThread = new TestSignalThread(reentrantLock, condition);
        signalThread.setName("test_signal");
        signalThread.start();
    }
1603284043331 Thread[test_condition_await,5,main] lock
1603284043331 Thread[test_condition_await,5,main]獲取到鎖
1603284043331 Thread[test_condition_await,5,main]awaitUninterruptibly start
1603284045332 Thread[main,5,main] interrupt  
1603284045340 Thread[test_signal,5,main] lock
1603284045340 Thread[test_signal,5,main]獲取到鎖後休眠21603284047347 Thread[test_signal,5,main] 傳送signalAll訊號
1603284047347 Thread[test_signal,5,main]釋放鎖
1603284047347 Thread[test_condition_await,5,main]awaitUninterruptibly end
1603284047347 Thread[test_condition_await,5,main]釋放鎖

傳送中斷訊號沒有打斷【test_condition_await】執行緒的等待。只有傳送訊號才能喚醒【test_condition_await】執行緒。

簡單用法

測試三個執行緒順序列印1,2,3…到100 - 實現一

通過公共狀態類維護

    //三個執行緒,順序執行,列印100
    private static void test1() {
        ReentrantLock reentrantLock = new ReentrantLock();
        Bean bean = new Bean(100, new AtomicInteger(0));
        TestOneRunnable testOneRunnable1 = new TestOneRunnable(reentrantLock, bean, 0);
        TestOneRunnable testOneRunnable2 = new TestOneRunnable(reentrantLock, bean, 1);
        TestOneRunnable testOneRunnable3 = new TestOneRunnable(reentrantLock, bean, 2);
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(testOneRunnable1);
        executorService.submit(testOneRunnable2);
        executorService.submit(testOneRunnable3);
        executorService.shutdown();
    }
    
    static class Bean {
        int index;
        volatile AtomicInteger status;
        int num;

        public Bean(int num, AtomicInteger status) {
            this.num = num;
            this.status = status;
        }
    }

    static class TestOneRunnable implements Runnable {
        
        ReentrantLock mReentrantLock;
        Bean mBean;
        int mFlag = -1;

        public TestOneRunnable(ReentrantLock reentrantLock, Bean bean, int flag) {
            this.mReentrantLock = reentrantLock;
            this.mBean = bean;
            this.mFlag = flag;
        }

        @Override
        public void run() {
            //System.out.println(Thread.currentThread() + " start");
            while (mBean.index < mBean.num) {
                try {
                    //1.獲得鎖
                    mReentrantLock.lock();
                    if (mBean.index >= mBean.num) {
                        return;
                    }
                    //改變狀態
                    if (mFlag == (mBean.status.intValue() % 3)) {
                        System.out.println(Thread.currentThread() + " " + mBean.index++);
                        mBean.status.incrementAndGet(); //狀態+1
                    }
                } finally {
                    //2.釋放重入鎖
                    mReentrantLock.unlock();
                }
            }
            //System.out.println(Thread.currentThread() + " end");
        }
    }

測試結果:

Thread[pool-1-thread-1,5,main] 0
Thread[pool-1-thread-2,5,main] 1
Thread[pool-1-thread-3,5,main] 2
Thread[pool-1-thread-1,5,main] 3
Thread[pool-1-thread-2,5,main] 4
Thread[pool-1-thread-3,5,main] 5
...
Thread[pool-1-thread-1,5,main] 93
Thread[pool-1-thread-2,5,main] 94
Thread[pool-1-thread-3,5,main] 95
Thread[pool-1-thread-1,5,main] 96
Thread[pool-1-thread-2,5,main] 97
Thread[pool-1-thread-3,5,main] 98
Thread[pool-1-thread-1,5,main] 99

三個執行緒迴圈列印數字到100,每個執行緒獲得鎖過後,判斷是否該自己列印,如果該自己列印,列印數字並改變陣列和狀態後,在釋放鎖,在嘗試獲取鎖,等待其他執行緒更改狀態滿足條件。

測試三個執行緒順序列印1,2,3…到100 - 實現二

通過Condition實現

    static class TestPrintThread extends Thread {
        ReentrantLock mReentrantLock;
        PrintBean mPrintBean;
        Condition mConditionCurr; //當前執行緒的條件
        Condition mConditionNext; //下一個執行緒的條件

        public TestPrintThread(ReentrantLock reentrantLock, PrintBean printBean, Condition conditionCurr, Condition conditionNext) {
            this.mReentrantLock = reentrantLock;
            this.mPrintBean = printBean;
            this.mConditionCurr = conditionCurr;
            this.mConditionNext = conditionNext;
        }

        @Override
        public void run() {
            //System.out.println(Thread.currentThread() + " start");
            while (mPrintBean.currentIndex < mPrintBean.max) {
                try {
                    //1.獲得鎖
                    mReentrantLock.lock();
                    if (mPrintBean.currentIndex >= mPrintBean.max) {
                        System.out.println(Thread.currentThread() + " end1");
                        mConditionNext.signal();
                        return;
                    }
                    System.out.println(Thread.currentThread() + " " + mPrintBean.currentIndex++);
                    //傳送訊號,喚醒下一個執行緒
                    mConditionNext.signal();
                    //當前執行緒等待
                    //System.out.println(Thread.currentThread() + " wait");
                    mConditionCurr.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //傳送訊號,喚醒下一個執行緒,防止下一次迴圈的時候直接退出,沒有傳送訊號
                    mConditionNext.signal();
                    //釋放重入鎖
                    mReentrantLock.unlock();
                }
            }
            System.out.println(Thread.currentThread() + " end2");
        }
    }

    //三個執行緒,順序執行,列印100
    private static void test100() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition1 = reentrantLock.newCondition();
        Condition condition2 = reentrantLock.newCondition();
        Condition condition3 = reentrantLock.newCondition();
        PrintBean printBean = new PrintBean(100, 0);
        TestPrintThread printRunnable1 = new TestPrintThread(reentrantLock, printBean, condition1, condition2);
        TestPrintThread printRunnable2 = new TestPrintThread(reentrantLock, printBean, condition2, condition3);
        TestPrintThread printRunnable3 = new TestPrintThread(reentrantLock, printBean, condition3, condition1);

        printRunnable1.start();
        printRunnable2.start();
        printRunnable3.start();
    }

用newCondition新建三個Condition,每個執行緒持有一個當前執行緒的Condition和下一個執行緒的Condition.當前執行緒列印後,先傳送下一個執行緒的喚醒訊號,在呼叫await進入等待。

Thread[Thread-3,5,main] 0
Thread[Thread-4,5,main] 1
Thread[Thread-5,5,main] 2
Thread[Thread-3,5,main] 3
Thread[Thread-4,5,main] 4
Thread[Thread-5,5,main] 5
...
Thread[Thread-4,5,main] 97
Thread[Thread-5,5,main] 98
Thread[Thread-3,5,main] 99
Thread[Thread-4,5,main] end2
Thread[Thread-5,5,main] end2
Thread[Thread-3,5,main] end2

三個執行緒迴圈列印0,1,2,3,…99.

相關文章