• <menu id="gyiem"><menu id="gyiem"></menu></menu>
  • <menu id="gyiem"><code id="gyiem"></code></menu>

    Java進階(三)多線程開發關鍵技術

    原創文章,轉載請務必將下面這段話置于文章開頭處(保留超鏈接)。
    本文轉發自技術世界原文鏈接 http://www.luozeyang.com/java/multi_thread/

    sleep和wait到底什么區別

    其實這個問題應該這么問——sleep和wait有什么相同點。因為這兩個方法除了都能讓當前線程暫停執行完,幾乎沒有其它相同點。

    wait方法是Object類的方法,這意味著所有的Java類都可以調用該方法。sleep方法是Thread類的靜態方法。

    wait是在當前線程持有wait對象鎖的情況下,暫時放棄鎖,并讓出CPU資源,并積極等待其它線程調用同一對象的notify或者notifyAll方法。注意,即使只有一個線程在等待,并且有其它線程調用了notify或者notifyAll方法,等待的線程只是被激活,但是它必須得再次獲得鎖才能繼續往下執行。換言之,即使notify被調用,但只要鎖沒有被釋放,原等待線程因為未獲得鎖仍然無法繼續執行。測試代碼如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import java.util.Date;

    public class Wait {

    public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
    synchronized (Wait.class) {
    try {
    System.out.println(new Date() + " Thread1 is running");
    Wait.class.wait();
    System.out.println(new Date() + " Thread1 ended");
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }
    });
    thread1.start();

    Thread thread2 = new Thread(() -> {
    synchronized (Wait.class) {
    try {
    System.out.println(new Date() + " Thread2 is running");
    Wait.class.notify();
    // Don't use sleep method to avoid confusing
    for(long i = 0; i < 200000; i++) {
    for(long j = 0; j < 100000; j++) {}
    }
    System.out.println(new Date() + " Thread2 release lock");
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }

    for(long i = 0; i < 200000; i++) {
    for(long j = 0; j < 100000; j++) {}
    }
    System.out.println(new Date() + " Thread2 ended");
    });

    // Don't use sleep method to avoid confusing
    for(long i = 0; i < 200000; i++) {
    for(long j = 0; j < 100000; j++) {}
    }
    thread2.start();
    }
    }

    執行結果如下

    1
    2
    3
    4
    5
    Tue Jun 14 22:51:11 CST 2016 Thread1 is running
    Tue Jun 14 22:51:23 CST 2016 Thread2 is running
    Tue Jun 14 22:51:36 CST 2016 Thread2 release lock
    Tue Jun 14 22:51:36 CST 2016 Thread1 ended
    Tue Jun 14 22:51:49 CST 2016 Thread2 ended

    從運行結果可以看出

    • thread1執行wait后,暫停執行
    • thread2執行notify后,thread1并沒有繼續執行,因為此時thread2尚未釋放鎖,thread1因為得不到鎖而不能繼續執行
    • thread2執行完synchronized語句塊后釋放鎖,thread1得到通知并獲得鎖,進而繼續執行

    注意:wait方法需要釋放鎖,前提條件是它已經持有鎖。所以wait和notify(或者notifyAll)方法都必須被包裹在synchronized語句塊中,并且synchronized后鎖的對象應該與調用wait方法的對象一樣。否則拋出IllegalMonitorStateException

    sleep方法告訴操作系統至少指定時間內不需為線程調度器為該線程分配執行時間片,并不釋放鎖(如果當前已經持有鎖)。實際上,調用sleep方法時并不要求持有任何鎖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.test.thread;

    import java.util.Date;

    public class Sleep {

    public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
    synchronized (Sleep.class) {
    try {
    System.out.println(new Date() + " Thread1 is running");
    Thread.sleep(2000);
    System.out.println(new Date() + " Thread1 ended");
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }
    });
    thread1.start();

    Thread thread2 = new Thread(() -> {
    synchronized (Sleep.class) {
    try {
    System.out.println(new Date() + " Thread2 is running");
    Thread.sleep(2000);
    System.out.println(new Date() + " Thread2 ended");
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }

    for(long i = 0; i < 200000; i++) {
    for(long j = 0; j < 100000; j++) {}
    }
    });

    // Don't use sleep method to avoid confusing
    for(long i = 0; i < 200000; i++) {
    for(long j = 0; j < 100000; j++) {}
    }
    thread2.start();
    }
    }

    執行結果如下

    1
    2
    3
    4
    Thu Jun 16 19:46:06 CST 2016 Thread1 is running
    Thu Jun 16 19:46:08 CST 2016 Thread1 ended
    Thu Jun 16 19:46:13 CST 2016 Thread2 is running
    Thu Jun 16 19:46:15 CST 2016 Thread2 ended

    由于thread 1和thread 2的run方法實現都在同步塊中,無論哪個線程先拿到鎖,執行sleep時并不釋放鎖,因此其它線程無法執行。直到前面的線程sleep結束并退出同步塊(釋放鎖),另一個線程才得到鎖并執行。

    注意:sleep方法并不需要持有任何形式的鎖,也就不需要包裹在synchronized中。

    本文所有示例均基于Java HotSpot(TM) 64-Bit Server VM

    調用sleep方法的線程,在jstack中顯示的狀態為sleeping

    1
    java.lang.Thread.State: TIMED_WAITING (sleeping)

    調用wait方法的線程,在jstack中顯示的狀態為on object monitor

    1
    java.lang.Thread.State: WAITING (on object monitor)

    synchronized幾種用法

    每個Java對象都可以用做一個實現同步的互斥鎖,這些鎖被稱為內置鎖。線程進入同步代碼塊或方法時自動獲得內置鎖,退出同步代碼塊或方法時自動釋放該內置鎖。進入同步代碼塊或者同步方法是獲得內置鎖的唯一途徑。

    實例同步方法

    synchronized用于修飾實例方法(非靜態方法)時,執行該方法需要獲得的是該類實例對象的內置鎖(同一個類的不同實例擁有不同的內置鎖)。如果多個實例方法都被synchronized修飾,則當多個線程調用同一實例的不同同步方法(或者同一方法)時,需要競爭鎖。但當調用的是不同實例的方法時,并不需要競爭鎖。

    靜態同步方法

    synchronized用于修飾靜態方法時,執行該方法需要獲得的是該類的class對象的內置鎖(一個類只有唯一一個class對象)。調用同一個類的不同靜態同步方法時會產生鎖競爭。

    同步代碼塊

    synchronized用于修飾代碼塊時,進入同步代碼塊需要獲得synchronized關鍵字后面括號內的對象(可以是實例對象也可以是class對象)的內置鎖。

    synchronized使用總結

    鎖的使用是為了操作臨界資源的正確性,而往往一個方法中并非所有的代碼都操作臨界資源。換句話說,方法中的代碼往往并不都需要同步。此時建議不使用同步方法,而使用同步代碼塊,只對操作臨界資源的代碼,也即需要同步的代碼加鎖。這樣做的好處是,當一個線程在執行同步代碼塊時,其它線程仍然可以執行該方法內同步代碼塊以外的部分,充分發揮多線程并發的優勢,從而相較于同步整個方法而言提升性能。

    釋放Java內置鎖的唯一方式是synchronized方法或者代碼塊執行結束。若某一線程在synchronized方法或代碼塊內發生死鎖,則對應的內置鎖無法釋放,其它線程也無法獲取該內置鎖(即進入跟該內置鎖相關的synchronized方法或者代碼塊)。

    使用jstack dump線程棧時,可查看到相關線程通過synchronized獲取到或等待的對象,但Locked ownable synchronizers仍然顯示為None。下例中,線程thead-test-b已獲取到類型為java.lang.Double的對象的內置鎖(monitor),且該對象的內存地址為0x000000076ab95cb8

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "thread-test-b" #11 prio=5 os_prio=31 tid=0x00007fab0190b800 nid=0x5903 runnable [0x0000700010249000]
    java.lang.Thread.State: RUNNABLE
    at com.jasongj.demo.TestJstack.lambda$1(TestJstack.java:27)
    - locked <0x000000076ab95cb8> (a java.lang.Double)
    at com.jasongj.demo.TestJstack$$Lambda$2/1406718218.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

    Locked ownable synchronizers:
    - None

    Java中的鎖

    重入鎖

    Java中的重入鎖(即ReentrantLock)與Java內置鎖一樣,是一種排它鎖。使用synchronized的地方一定可以用ReentrantLock代替。

    重入鎖需要顯示請求獲取鎖,并顯示釋放鎖。為了避免獲得鎖后,沒有釋放鎖,而造成其它線程無法獲得鎖而造成死鎖,一般建議將釋放鎖操作放在finally塊里,如下所示。

    1
    2
    3
    4
    5
    6
    try{
    renentrantLock.lock();
    // 用戶操作
    } finally {
    renentrantLock.unlock();
    }

    如果重入鎖已經被其它線程持有,則當前線程的lock操作會被阻塞。除了lock()方法之外,重入鎖(或者說鎖接口)還提供了其它獲取鎖的方法以實現不同的效果。

    • lockInterruptibly() 該方法嘗試獲取鎖,若獲取成功立即返回;若獲取不成功則阻塞等待。與lock方法不同的是,在阻塞期間,如果當前線程被打斷(interrupt)則該方法拋出InterruptedException。該方法提供了一種解除死鎖的途徑。
    • tryLock() 該方法試圖獲取鎖,若該鎖當前可用,則該方法立即獲得鎖并立即返回true;若鎖當前不可用,則立即返回false。該方法不會阻塞,并提供給用戶對于成功獲利鎖與獲取鎖失敗進行不同操作的可能性。
    • tryLock(long time, TimeUnit unit) 該方法試圖獲得鎖,若該鎖當前可用,則立即獲得鎖并立即返回true。若鎖當前不可用,則等待相應的時間(由該方法的兩個參數決定):1)若該時間內鎖可用,則獲得鎖,并返回true;2)若等待期間當前線程被打斷,則拋出InterruptedException;3)若等待時間結束仍未獲得鎖,則返回false。

    重入鎖可定義為公平鎖或非公平鎖,默認實現為非公平鎖。

    • 公平鎖是指多個線程獲取鎖被阻塞的情況下,鎖變為可用時,最新申請鎖的線程獲得鎖。可通過在重入鎖(RenentrantLock)的構造方法中傳入true構建公平鎖,如Lock lock = new RenentrantLock(true)
    • 非公平鎖是指多個線程等待鎖的情況下,鎖變為可用狀態時,哪個線程獲得鎖是隨機的。synchonized相當于非公平鎖。可通過在重入鎖的構造方法中傳入false或者使用無參構造方法構建非公平鎖。

    使用jstack dump線程棧時,可查看到獲取到或正在等待的鎖對象,獲取到該鎖的線程會在Locked ownable synchronizers處顯示該鎖的對象類型及內存地址。在下例中,從Locked ownable synchronizers部分可看到,線程thread-test-e獲取到公平重入鎖,且該鎖對象的內存地址為0x000000076ae3d708

    1
    2
    3
    4
    5
    6
    7
    8
    "thread-test-e" #17 prio=5 os_prio=31 tid=0x00007fefaa0b6800 nid=0x6403 runnable [0x0000700002939000]
    java.lang.Thread.State: RUNNABLE
    at com.jasongj.demo.TestJstack.lambda$4(TestJstack.java:64)
    at com.jasongj.demo.TestJstack$$Lambda$5/466002798.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

    Locked ownable synchronizers:
    - <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)

    而線程thread-test-f由于未獲取到鎖,而處于WAITING(parking)狀態,且它等待的鎖正是上文線程thread-test-e獲取的鎖(內存地址0x000000076af86810

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    "thread-test-f" #18 prio=5 os_prio=31 tid=0x00007fefaa9b2800 nid=0x6603 waiting on condition [0x0000700002a3c000]
    java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)
    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.acquireQueued(AbstractQueuedSynchronizer.java:870)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
    at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    at com.jasongj.demo.TestJstack.lambda$5(TestJstack.java:69)
    at com.jasongj.demo.TestJstack$$Lambda$6/33524623.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)
    Locked ownable synchronizers:
    - None

    讀寫鎖

    如上文《Java進階(二)當我們說線程安全時,到底在說什么》所述,鎖可以保證原子性和可見性。而原子性更多是針對寫操作而言。對于讀多寫少的場景,一個讀操作無須阻塞其它讀操作,只需要保證讀和寫或者寫與寫不同時發生即可。此時,如果使用重入鎖(即排它鎖),對性能影響較大。Java中的讀寫鎖(ReadWriteLock)就是為這種讀多寫少的場景而創造的。

    實際上,ReadWriteLock接口并非繼承自Lock接口,ReentrantReadWriteLock也只實現了ReadWriteLock接口而未實現Lock接口。ReadLock和WriteLock,是ReentrantReadWriteLock類的靜態內部類,它們實現了Lock接口。

    一個ReentrantReadWriteLock實例包含一個ReentrantReadWriteLock.ReadLock實例和一個ReentrantReadWriteLock.WriteLock實例。通過readLock()writeLock()方法可分別獲得讀鎖實例和寫鎖實例,并通過Lock接口提供的獲取鎖方法獲得對應的鎖。

    讀寫鎖的鎖定規則如下:

    • 獲得讀鎖后,其它線程可獲得讀鎖而不能獲取寫鎖
    • 獲得寫鎖后,其它線程既不能獲得讀鎖也不能獲得寫鎖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    package com.test.thread;

    import java.util.Date;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;

    public class ReadWriteLockDemo {

    public static void main(String[] args) {
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    new Thread(() -> {
    readWriteLock.readLock().lock();
    try {
    System.out.println(new Date() + "\tThread 1 started with read lock");
    try {
    Thread.sleep(2000);
    } catch (Exception ex) {
    }
    System.out.println(new Date() + "\tThread 1 ended");
    } finally {
    readWriteLock.readLock().unlock();
    }
    }).start();

    new Thread(() -> {
    readWriteLock.readLock().lock();
    try {
    System.out.println(new Date() + "\tThread 2 started with read lock");
    try {
    Thread.sleep(2000);
    } catch (Exception ex) {
    }
    System.out.println(new Date() + "\tThread 2 ended");
    } finally {
    readWriteLock.readLock().unlock();
    }
    }).start();

    new Thread(() -> {
    Lock lock = readWriteLock.writeLock();
    lock.lock();
    try {
    System.out.println(new Date() + "\tThread 3 started with write lock");
    try {
    Thread.sleep(2000);
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    System.out.println(new Date() + "\tThread 3 ended");
    } finally {
    lock.unlock();
    }
    }).start();
    }
    }

    執行結果如下

    1
    2
    3
    4
    5
    6
    Sat Jun 18 21:33:46 CST 2016  Thread 1 started with read lock
    Sat Jun 18 21:33:46 CST 2016 Thread 2 started with read lock
    Sat Jun 18 21:33:48 CST 2016 Thread 2 ended
    Sat Jun 18 21:33:48 CST 2016 Thread 1 ended
    Sat Jun 18 21:33:48 CST 2016 Thread 3 started with write lock
    Sat Jun 18 21:33:50 CST 2016 Thread 3 ended

    從上面的執行結果可見,thread 1和thread 2都只需獲得讀鎖,因此它們可以并行執行。而thread 3因為需要獲取寫鎖,必須等到thread 1和thread 2釋放鎖后才能獲得鎖。

    條件鎖

    條件鎖只是一個幫助用戶理解的概念,實際上并沒有條件鎖這種鎖。對于每個重入鎖,都可以通過newCondition()方法綁定若干個條件對象。

    條件對象提供以下方法以實現不同的等待語義

    • await() 調用該方法的前提是,當前線程已經成功獲得與該條件對象綁定的重入鎖,否則調用該方法時會拋出IllegalMonitorStateException。調用該方法外,當前線程會釋放當前已經獲得的鎖(這一點與上文講述的Java內置鎖的wait方法一致),并且等待其它線程調用該條件對象的signal()或者signalAll()方法(這一點與Java內置鎖wait后等待notify()notifyAll()很像)。或者在等待期間,當前線程被打斷,則wait()方法會拋出InterruptedException并清除當前線程的打斷狀態。
    • await(long time, TimeUnit unit) 適用條件和行為與await()基本一致,唯一不同點在于,指定時間之內沒有收到signal()signalALL()信號或者線程中斷時該方法會返回false;其它情況返回true。
    • awaitNanos(long nanosTimeout) 調用該方法的前提是,當前線程已經成功獲得與該條件對象綁定的重入鎖,否則調用該方法時會拋出IllegalMonitorStateExceptionnanosTimeout指定該方法等待信號的的最大時間(單位為納秒)。若指定時間內收到signal()signalALL()則返回nanosTimeout減去已經等待的時間;若指定時間內有其它線程中斷該線程,則拋出InterruptedException并清除當前線程的打斷狀態;若指定時間內未收到通知,則返回0或負數。
    • awaitUninterruptibly() 調用該方法的前提是,當前線程已經成功獲得與該條件對象綁定的重入鎖,否則調用該方法時會拋出IllegalMonitorStateException。調用該方法后,結束等待的唯一方法是其它線程調用該條件對象的signal()signalALL()方法。等待過程中如果當前線程被中斷,該方法仍然會繼續等待,同時保留該線程的中斷狀態。
    • awaitUntil(Date deadline) 適用條件與行為與awaitNanos(long nanosTimeout)完全一樣,唯一不同點在于它不是等待指定時間,而是等待由參數指定的某一時刻。

    調用條件等待的注意事項

    • 調用上述任意條件等待方法的前提都是當前線程已經獲得與該條件對象對應的重入鎖。
    • 調用條件等待后,當前線程讓出CPU資源。
    • 上述等待方法結束后,方法返回的前提是它能重新獲得與該條件對象對應的重入鎖。如果無法獲得鎖,仍然會繼續等待。這也是awaitNanos(long nanosTimeout)可能會返回負值的原因。
    • 一旦條件等待方法返回,則當前線程肯定已經獲得了對應的重入鎖。
    • 重入鎖可以創建若干個條件對象,signal()signalAll()方法只能喚醒相同條件對象的等待。
    • 一個重入鎖上可以生成多個條件變量,不同線程可以等待不同的條件,從而實現更加細粒度的的線程間通信。

    signal()signalAll()

    • signal() 若有一個或若干個線程在等待該條件變量,則該方法會喚醒其中的一個(具體哪一個,無法預測)。調用該方法的前提是當前線程持有該條件變量對應的鎖,否則拋出IllegalMonitorStateException
    • signalALL() 若有一個或若干個線程在等待該條件變量,則該方法會喚醒所有等待。調用該方法的前提是當前線程持有該條件變量對應的鎖,否則拋出IllegalMonitorStateException
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package com.test.thread;

    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    public class ConditionTest {

    public static void main(String[] args) throws InterruptedException {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    new Thread(() -> {
    lock.lock();
    try {
    System.out.println(new Date() + "\tThread 1 is waiting");
    try {
    long waitTime = condition.awaitNanos(TimeUnit.SECONDS.toNanos(2));
    System.out.println(new Date() + "\tThread 1 remaining time " + waitTime);
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    System.out.println(new Date() + "\tThread 1 is waken up");
    } finally {
    lock.unlock();
    }
    }).start();

    new Thread(() -> {
    lock.lock();
    try{
    System.out.println(new Date() + "\tThread 2 is running");
    try {
    Thread.sleep(4000);
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    condition.signal();
    System.out.println(new Date() + "\tThread 2 ended");
    } finally {
    lock.unlock();
    }
    }).start();
    }
    }

    執行結果如下

    1
    2
    3
    4
    5
    Sun Jun 19 15:59:09 CST 2016  Thread 1 is waiting
    Sun Jun 19 15:59:09 CST 2016 Thread 2 is running
    Sun Jun 19 15:59:13 CST 2016 Thread 2 ended
    Sun Jun 19 15:59:13 CST 2016 Thread 1 remaining time -2003467560
    Sun Jun 19 15:59:13 CST 2016 Thread 1 is waken up

    從執行結果可以看出,雖然thread 2一開始就調用了signal()方法去喚醒thread 1,但是因為thread 2在4秒鐘后才釋放鎖,也即thread 1在4秒后才獲得鎖,所以thread 1的await方法在4秒鐘后才返回,并且返回負值。

    信號量Semaphore

    信號量維護一個許可集,可通過acquire()獲取許可(若無可用許可則阻塞),通過release()釋放許可,從而可能喚醒一個阻塞等待許可的線程。

    與互斥鎖類似,信號量限制了同一時間訪問臨界資源的線程的個數,并且信號量也分公平信號量與非公平信號量。而不同的是,互斥鎖保證同一時間只會有一個線程訪問臨界資源,而信號量可以允許同一時間多個線程訪問特定資源。所以信號量并不能保證原子性。

    信號量的一個典型使用場景是限制系統訪問量。每個請求進來后,處理之前都通過acquire獲取許可,若獲取許可成功則處理該請求,若獲取失敗則等待處理或者直接不處理該請求。

    信號量的使用方法

    • acquire(int permits) 申請permits(必須為非負數)個許可,若獲取成功,則該方法返回并且當前可用許可數減permits;若當前可用許可數少于permits指定的個數,則繼續等待可用許可數大于等于permits;若等待過程中當前線程被中斷,則拋出InterruptedException
    • acquire() 等價于acquire(1)
    • acquireUninterruptibly(int permits) 申請permits(必須為非負數)個許可,若獲取成功,則該方法返回并且當前可用許可數減permits;若當前許可數少于permits,則繼續等待可用許可數大于等于permits;若等待過程中當前線程被中斷,繼續等待可用許可數大于等于permits,并且獲取成功后設置線程中斷狀態。
    • acquireUninterruptibly() 等價于acquireUninterruptibly(1)
    • drainPermits() 獲取所有可用許可,并返回獲取到的許可個數,該方法不阻塞。
    • tryAcquire(int permits) 嘗試獲取permits個可用許可,如果當前許可個數大于等于permits,則返回true并且可用許可數減permits;否則返回false并且可用許可數不變。
    • tryAcquire() 等價于tryAcquire(1)
    • tryAcquire(int permits, long timeout, TimeUnit unit) 嘗試獲取permits(必須為非負數)個許可,若在指定時間內獲取成功則返回true并且可用許可數減permits;若指定時間內當前線程被中斷,則拋出InterruptedException;若指定時間內可用許可數均小于permits,則返回false。
    • tryAcquire(long timeout, TimeUnit unit) 等價于tryAcquire(1, long timeout, TimeUnit unit)*
    • release(int permits) 釋放permits個許可,該方法不阻塞并且某線程調用release方法前并不需要先調用acquire方法。
    • release() 等價于release(1)

    注意:與wait/notify和await/signal不同,acquire/release完全與鎖無關,因此acquire等待過程中,可用許可滿足要求時acquire可立即返回,而不用像鎖的wait和條件變量的await那樣重新獲取鎖才能返回。或者可以理解成,只要可用許可滿足需求,就已經獲得了鎖。

    Java進階系列

    郭俊 Jason wechat
    歡迎關注作者微信公眾號【大數據架構】
    您的贊賞將支持作者繼續原創分享
    速赢彩app 安康 | 日照 | 惠州 | 桂林 | 澳门澳门 | 晋江 | 塔城 | 武安 | 温州 | 桐城 | 宜都 | 三亚 | 库尔勒 | 滕州 | 乌兰察布 | 海西 | 贵港 | 山南 | 铜川 | 牡丹江 | 东方 | 咸宁 | 溧阳 | 永康 | 鄢陵 | 梅州 | 东营 | 江苏苏州 | 汕尾 | 乳山 | 六盘水 | 石狮 | 泗洪 | 临沧 | 巢湖 | 德阳 | 泰安 | 三亚 | 平凉 | 佛山 | 宝应县 | 铁岭 | 日喀则 | 泰州 | 达州 | 阿里 | 咸阳 | 禹州 | 林芝 | 醴陵 | 潍坊 | 怒江 | 新乡 | 云南昆明 | 舟山 | 琼中 | 武威 | 深圳 | 丹东 | 鹤壁 | 本溪 | 揭阳 | 烟台 | 定安 | 阜新 | 台中 | 香港香港 | 嘉兴 | 昌都 | 常德 | 长治 | 海拉尔 | 秦皇岛 | 淮南 | 海南海口 | 桓台 | 海西 | 苍南 | 延安 | 龙岩 | 阿坝 | 包头 | 伊犁 | 焦作 | 抚顺 | 芜湖 | 湖北武汉 | 达州 | 江西南昌 | 台山 | 文昌 | 宜昌 | 新余 | 中山 | 韶关 | 金坛 | 丹阳 | 十堰 | 阿里 | 抚州 | 双鸭山 | 保定 | 宿州 | 伊犁 | 海南海口 | 安徽合肥 | 泗洪 | 诸城 | 建湖 | 柳州 | 永新 | 阳春 | 怒江 |