文章目錄
源碼地址:https://github.com/nieandsun/concurrent-study.git
該工具類,在併發編程中主要用來進行併發控制,或者說限流
1 原理簡介
Semaphore是信號的意思,但是我覺得將其理解爲許可
或者說令牌好像更好理解一些,其原理可以用下圖進行表示:
即n個線程都想搶佔運行某段代碼,但是在它們運行之前必須得先去獲取指定數量的許可,比如說:
- (1)總共的許可就3個,哪個線程想要運行這段代碼,必須獲取1個許可
- 假如某個時間線程1、線程2、線程3獲取到了許可,則這三個線程就可以運行這段代碼了。其他沒獲取到的,必須等這三個線程中有一個釋放了許可,其他線程才能拿到許可,並執行這段代碼 —> 即併發數爲3。
再比如說:
- (2)總共的許可還是就3個,哪個線程想要運行這段代碼,必須獲取3個許可
- 那麼假如某個時間線程1獲取到了3個許可(
注意:
如果是指定必須獲取3個許可,那麼不會有線程一次只拿到1個或2個的情況,即要麼拿到3個,要麼1個沒拿到
),那其他線程就沒法獲取許可了,這個時候同一時刻,就只能有一個線程運行該方法了 —》即不存在併發的情況了。
我
自認爲
通過上面兩個栗子大家肯定知道Semaphore的使用原理了。
2 基本使用方法
2.1 demo1 — 每次獲取一個許可,將線程併發數控制爲N個
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo1 {
private final static int threadCount = 12;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); //獲取一個許可
//總共3個許可,而每次只需拿到一個許可就可以運行下面的方法
//也就是說同一時刻可以允許三個線程拿到許可,即併發數爲3
test(threadNum);
semaphore.release(); //釋放一個許可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int i) throws InterruptedException {
Thread.sleep(2000);
log.info("threadNum:{}", i);
}
}
- 測試結果
2.2 demo2 — 每次獲取多個許可(或者說所有可獲取的許可),使線程併發數變爲1
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo2 {
private final static int threadCount = 4;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(3); //獲取3個許可
//總共3個許可,每次都全獲取了
//也就是說同一時刻只允許一個線程拿到許可,即併發數爲1
test(threadNum);
semaphore.release(3); //釋放3個許可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int i) throws InterruptedException {
Thread.sleep(2000);
log.info("threadNum:{}", i);
}
}
- 測試結果:
3 其他玩法
Semaphore提供的方法還挺多的,如下圖所示,但大都大同小異。相信大家看看源碼中的註釋肯定就知道是幹什麼的了。 我這裏再做兩個小demo演示一下tryAcquire的用法,其他用法大家可以自行探索。
如上圖所示,tryAcquire方法有四種使用姿勢:
- tryAcquire() — 嘗試獲取一個許可
- tryAcquire(long timeout, TimeUnit unit) — 嘗試在某個時間段內獲取一個許可
- tryAcquire(int permits) — 嘗試獲取多個許可
- tryAcquire(int permits, long timeout, TimeUnit unit) — 嘗試在某個時間段內獲取多個許可
看到這裏我覺得大家應該都直接會用了。。。☺☺☺
需要注意的一點是:
這些方法的返回值都是boolean ,這也就提示我們,當線程競爭特別激烈
的時候,我們完全可以讓某些線程嘗試獲取一下執行權,如果實在獲取不到,爲了更好地保護我們的系統,我們就直接將其捨棄 —要非常注意一下這裏的捨棄
。
下面舉兩個栗子,簡單演示一下其用法。
3.1 demo3 — 嘗試獲取許可,如果獲取不到,直接捨棄 ★★★
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo3 {
private final static int threadCount = 520;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
if (semaphore.tryAcquire()) { //嘗試獲取1個許可
//總共3個許可,而每次只需拿到一個許可就可以運行下面的方法
//也就是說同一時刻可以允許三個線程拿到許可,即併發數爲3
test(threadNum);
semaphore.release(); //釋放3個許可
}
});
}
exec.shutdown();
}
private static void test(int i) {
try {
Thread.sleep(1); //這裏即使註釋掉也是一樣的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadNum:{}", i);
}
}
在不看運行結果時,你會不會認爲能運行test方法的線程數在(3,520)的
開區間內
,---- 即一開始會有3個線程搶到許可,然後1毫秒之後他們釋放掉許可,其他還沒開啓的線程正好又可以搶到許可???
but,事實並非如此,運行結果如下:
由這個結果我們可以看到,事實是隻要有一個線程沒搶到運行的許可,其他線程就也別想再運行了。。。
是不是很神奇!!! —> 有興趣的看源碼研究到底爲什麼吧,我這裏就不深入了。
3.2 demo4 — 嘗試一段時間內獲取許可,如果獲取不到,直接捨棄 ★★★
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SemaphoreDemo4 {
private final static int threadCount = 520;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
//5秒內嘗試獲取1個許可
if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
test(threadNum);
semaphore.release(); //釋放3個許可
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
exec.shutdown();
}
private static void test(int i) {
try {
Thread.sleep(1000); //這裏即使註釋掉也是一樣的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadNum:{}", i);
}
}
- 運行結果如下:
由此可知,加上時間也是隻要有一個線程嘗試獲取不到許可了,其他線程就也別想再運行了。。。
end