Semaphore 簡介
本文將對Semaphore類中的全部方法進行案例式的實驗,這樣可以全面地瞭解此類提供類哪些核心功能。
此類的主要作用就是限制線程併發的數量,如果不限制線程併發的數量,CPU資源很快就會被耗盡,每個線程執行的任務是相當緩慢,因為CPU把時間片分配給不同的線程對象,而且上下文切換也要耗時,最終造成系統運行效率大幅降低,所以限制併發線程的數量還是非常有必要的。
Semaphore的同步性
類Semaphore所提供的功能完全就是sybchronized關鍵字的升級版,但它提供的功能更加的強大與方便,主要作用就是控制線程併發的數量,而這一點,單純地使用synchronized是做不到的。
我們首先通過一個初步的案例來看看Semaphore類是如何實現限制線程併發數的。
Thread-4 1522991382314開始執行!
Thread-4 1522991383318執行結束!
Thread-3 1522991383319開始執行!
Thread-3 1522991384324執行結束!
Thread-0 1522991384325開始執行!
Thread-0 1522991385327執行結束!
Thread-1 1522991385327開始執行!
Thread-1 1522991386329執行結束!
Thread-2 1522991386329開始執行!
Thread-2 1522991387331執行結束!
類Semaphore的構造函數參數permits是許可的意思,代表同一時間內,最多允許多少個線程同時執行acquire()和release()之間的代碼。所以上面的示例中,最多允許1個線程執行acquire()和release()之間的代碼,所以打印的結果就是5個線程是同步的。
其實還可以傳入>1的許可,代表同一時間內,最多允許x個線程可以執行acquire()和release()之間的代碼。
如果將上面示例中Semaphore的構造函數參數permits改為2:
執行結果:
Thread-0 1522991932240開始執行!
Thread-4 1522991932240開始執行!
Thread-4 1522991933244執行結束!
Thread-0 1522991933244執行結束!
Thread-3 1522991933245開始執行!
Thread-1 1522991933245開始執行!
Thread-3 1522991934248執行結束!
Thread-1 1522991934248執行結束!
Thread-2 1522991934248開始執行!
Thread-2 1522991935253執行結束!
需要說明一下,對Semaphore類的構造方法傳遞的參數permits值如果大於1時,該類並不能保證線程安全性,因為還是有可能出現多個線程共同訪問實例變量,導致出現髒數據的情況。
方法acquire(int permits)參數作用及動態添加permits許可數量
有參方法acquire(int permits)功能是沒調用一次此方法,就使用x個許可。無參的acquire()作用是使用1個許可。
執行結果:
Thread-2 1522993586620開始執行!
Thread-9 1522993586620開始執行!
Thread-6 1522993586620開始執行!
Thread-0 1522993586620開始執行!
Thread-1 1522993586620開始執行!
Thread-2 1522993587096執行結束!
Thread-8 1522993587097開始執行!
Thread-0 1522993587212執行結束!
Thread-8 1522993587212執行結束!
Thread-5 1522993587213開始執行!
Thread-3 1522993587212開始執行!
Thread-6 1522993587361執行結束!
Thread-4 1522993587362開始執行!
Thread-9 1522993587531執行結束!
Thread-7 1522993587531開始執行!
Thread-1 1522993587557執行結束!
Thread-3 1522993587949執行結束!
Thread-4 1522993587991執行結束!
Thread-5 1522993588139執行結束!
Thread-7 1522993588158執行結束!
代碼中有10個許可,每次執行代碼semaphore.acquire(2);時消耗掉2個,所有10/2=5,說明同一時間只有5個線程允許執行acquire()和release()之間的代碼。
Semaphore類構造參數new Semaphore(5);中的5並不是最終的許可數,僅僅是初始的狀態值。release(int permits),可以動態添加permits的個數,無參release()作用是添加1個許可。
執行結果:
0
6
10
方法availablePermits()和drainPermits()
availablePermits()返回此Semaphore對象中當前可用的許可數,此方法通常用於調試,因為許可的數量可能實時在改變,並不是固定的數量。
drainPermits()可獲取並返回立即可用的所有許可個數,並且將可用許可置0。
執行結果:
3
3
0 0
方法acquireUninterruptibly()的使用
方法acquireUninterruptibly()的作用是使等待進入acquire()方法的線程,不允許被中斷。
執行結果:
A開始執行!
main……end!
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
at com.thread.Semaphore.SemaphoreTest$Service.test(SemaphoreTest.java:17)
at com.thread.Semaphore.SemaphoreTest$MyThread.run(SemaphoreTest.java:36)
A執行結束!
上面示例未使用acquireUninterruptibly()所以,在現在B等待進入許可的時候被成功中斷了。如果將示例中代碼改成使用acquireUninterruptibly():
semaphore.acquireUninterruptibly();
執行結果:
A開始執行!
main……end!
A執行結束!
B開始執行!
B執行結束!
acquireUninterruptibly()方法還有重載的寫法acquireUninterruptibly(int permits),此方法的作用是在等待許可的情況下不允許中斷,如果成功獲得鎖,則取得制定的permits許可個數。
公平與非公平信號量測試
所謂的公平信號量是獲得鎖的順序與線程啟動的順序有關,但不代表100%地獲得信號量,僅僅是在概率上能得到保證。而非公平信號量就是無關的量。
執行結果
Thread-2啟動了!
Thread-0啟動了!
Thread-1啟動了!
Thread-4啟動了!
Thread-3啟動了!
Thread-2開始執行!
Thread-5啟動了!
Thread-5開始執行!
Thread-1開始執行!
Thread-0開始執行!
Thread-4開始執行!
Thread-3開始執行!
更改Service代碼如下:
private boolean isFair = true;
執行結果:
Thread-1啟動了!
Thread-4啟動了!
Thread-1開始執行!
Thread-2啟動了!
Thread-0啟動了!
Thread-3啟動了!
Thread-5啟動了!
Thread-4開始執行!
Thread-2開始執行!
Thread-0開始執行!
Thread-3開始執行!
Thread-5開始執行!
由此可見,非公平信號量允許的效果是線程啟動的順序與調用semaphore.acquire();的順序無關,也就是線程先啟動並不代表先獲得許可。公平信號量的運行效果是線程啟動的順序與調用semaphore.acquire();的順序有關,也就是先啟動的線程優先獲得許可。
方法tryAcquire()的使用
無參方法tryAcquire()的作用是嘗試地獲得1個許可,如果獲取不到則返回false,此方法通常與if語句結合使用,其具有無阻塞的特點。無阻塞的特點可以使線程不至於在同步處一直持續等待的狀態,如果if語句判斷不成立則線程會繼續走else語句,程序會繼續向下運行。
執行結果:
A開始執行!
B未成功進入!
tryAcquire(int permits)的作用是嘗試地獲得x個許可,如果獲取不到則返回false。
tryAcquire(long timeout,TimeUnit unit)的作用是在指定的時間內嘗試地獲得1個許可,如果獲取不到則返回false。
tryAcquire(int permits,long timeout,TimeUnit unit)的作用是在指定時間內嘗試地獲得x個許可,如果獲取不到則返回false
絕對乾貨!!!持續更新!!!
如果您喜歡請加關注!!!