目錄
一、AQS組件——信號量Semaphore
1. 信號量(Semaphore
)介紹:
- 作用:用於保證同一時間併發訪問線程的數目。
- 信號量在操作系統中是很重要的概念,Java併發庫裏的
Semaphore
就可以很輕鬆的完成類似操作系統信號量的控制; Semaphore
使用acquire
方法和release
方法來實現控制;- 在數據結構中我們學過鏈表,鏈表正常是可以保存無限個節點的,而
Semaphore
可以實現有限大小的列表。
2.使用場景
僅能提供有限訪問的資源。比如數據庫連接。
二、信號量(Semaphore
)的使用
1.允許單一許可場景
期望:有20個線程,每次只允許3個線程同時執行test()
方法,具體代碼如下:
@Slf4j
public class SemaphorehExample1 {
private static final int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//實例化Semaphore,並設置允許的併發數
final Semaphore semaphore = new Semaphore(3);
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
semaphore.acquire(); //拿到一個許可,執行後面的test()語句
test(threadNum);
semaphore.release();//釋放一個許可
}catch (Exception e){
log.error("exception",e);
}finally {
}
});
}
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
log.info("{}",threadNum);
Thread.sleep(1000);
}
}
執行結果部分內容如下:
...
23:06:41.896 [pool-1-thread-2] INFO com.mmall.concurrency.example.aqs.SemaphorehExample1 - 1
23:06:41.896 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.SemaphorehExample1 - 0
23:06:41.903 [pool-1-thread-3] INFO com.mmall.concurrency.example.aqs.SemaphorehExample1 - 2
23:06:41.905 [pool-1-thread-4] INFO com.mmall.concurrency.example.aqs.SemaphorehExample1 - 3
23:06:41.905 [pool-1-thread-5] INFO com.mmall.concurrency.example.aqs.SemaphorehExample1 - 4
...
- 可以看到,控制檯日誌每隔一秒就出現一塊日誌,而且每一個塊都是隻有3條日誌,也就說每秒只有同時只有3個線程同時在執行
test()
命令; - 線程執行順序沒有規律;
2.允許多個許可場景
代碼示例:
@Slf4j
public class SemaphorehExample2 {
private static final int threadCount = 20;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//實例化Semaphore,並設置允許的併發數
final Semaphore semaphore = new Semaphore(3);
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
semaphore.acquire(3); //拿到3個許可,執行後面的test()語句
test(threadNum);
semaphore.release(3);//釋放3個許可
}catch (Exception e){
log.error("exception",e);
}finally {
}
});
}
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
log.info("{}",threadNum);
Thread.sleep(1000);
}
}
- 上述代碼運行結果會是:逐行逐行地輸出日誌。因爲設置了總共只有3個許可,而每次運行一個線程,就需要消耗3個許可,其他線程無法拿到足夠的許可去運行代碼;
- 上述的代碼邏輯,等同於單線程程序
線程拿到多個許可之後,不一定需要一次性全部釋放掉,可以逐個釋放許可。
3. 超過線程數就丟棄場景
場景:假如當前允許併發數爲3個,超哥3個的話,就丟棄掉多的線程。代碼示例如下:
@Slf4j
public class SemaphorehExample3 {
private static final int threadCount = 20;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//實例化Semaphore,並設置允許的併發數爲3
final Semaphore semaphore = new Semaphore(3);
for (int i=0; i<threadCount; i++){
final int threadNum = i;
executorService.execute(()->{
try {
//嘗試去拿許可,如果拿到了就執行後面的test()語句,否則就丟棄該線程
if(semaphore.tryAcquire()){
test(threadNum);
}
semaphore.release();//釋放一個許可
}catch (Exception e){
log.error("exception",e);
}finally {
}
});
}
executorService.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
log.info("{}",threadNum);
Thread.sleep(1000);
}
}
執行結果:
23:32:09.549 [pool-1-thread-2] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 1
23:32:09.550 [pool-1-thread-5] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 4
23:32:09.553 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 0
23:32:09.550 [pool-1-thread-3] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 2
23:32:09.553 [pool-1-thread-8] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 7
23:32:09.557 [pool-1-thread-11] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 10
23:32:09.559 [pool-1-thread-13] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 12
23:32:09.561 [pool-1-thread-15] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 14
23:32:09.562 [pool-1-thread-17] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 16
23:32:09.562 [pool-1-thread-19] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 18
23:32:09.562 [pool-1-thread-6] INFO com.mmall.concurrency.example.aqs.SemaphorehExample3 - 5
Process finished with exit code 0
執行結果分析:
- 每一次執行,最終輸出的日誌的行數都有可能不同;
- 沒有輸出的日誌,說明該線程因爲沒獲取到許可,直接被丟棄掉了;
- 上述情況,是申請實時獲取許可,
tryAcquire()
方法中可以設置等待一段時間,線程可以在該時間段內申請許可,沒有申請到則被拋棄;
4.其他場景使用
tryAcquire()
方法提供了多個帶參數的方法:
源碼如下:
* @param permits the number of permits to acquire
* @return {@code true} if the permits were acquired and
* {@code false} otherwise
* @throws IllegalArgumentException if {@code permits} is negative
*/
//設置每次嘗試獲取的許可數
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
* @param timeout the maximum time to wait for a permit
* @param unit the time unit of the {@code timeout} argument
* @return {@code true} if a permit was acquired and {@code false}
* if the waiting time elapsed before a permit was acquired
* @throws InterruptedException if the current thread is interrupted
*/
//設置每次運行獲取許可的等待時間以及時間單位
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
* @param permits the number of permits to acquire
* @param timeout the maximum time to wait for the permits
* @param unit the time unit of the {@code timeout} argument
* @return {@code true} if all permits were acquired and {@code false}
* if the waiting time elapsed before all permits were acquired
* @throws InterruptedException if the current thread is interrupted
* @throws IllegalArgumentException if {@code permits} is negative
*/
//該方法是上面兩個方法的總和,即設置在允許時間段內可以獲取的許可數
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
上述方法的使用,此處只演示其中tryAcquire(long timeout, TimeUnit unit)
方法的關鍵示例代碼:
try {
if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) { // 嘗試獲取一個許可
test(threadNum);
semaphore.release(); // 釋放一個許可
}
} catch (Exception e) {
log.error("exception", e);
}
Java併發編程學習系列
如有幫助,煩請點贊收藏一下啦 (◕ᴗ◕✿)