Semaphore信號量
最多允許N個線程同時執行,如果信號量的許可設置爲1與ReentrantLock效果一致。
以下示例重點是Semaphore的基本使用,忽略CountDownLatch。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信號量
*
* @author Administrator
* @date 2020年10月15日
*/
public class Semaphore_test {
static int count = 0;
//併發許可爲 5
static Semaphore s = new Semaphore(5); //如果許可設置爲1與ReentrantLock效果一致。
static CountDownLatch c = new CountDownLatch(10); //閉鎖
public static void main(String[] args) {
for(int i=0; i<10; i++) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(1); //方便模擬出count是線程不安全的狀態值
} catch (InterruptedException e) {
e.printStackTrace();
}
count();
c.countDown();
}).start();
}
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
}
//count方法最多5個線程同時執行
public static void count() {
try {
s.acquire();
for(int i=0; i<1000; i++) {
count++;
}
} catch (InterruptedException e) {
}finally {
s.release();
}
}
}
信號量主要的兩個方法獲取許可acquire方法和釋放許可release方法。還有一個有參構造方法。
public Semaphore(int permits) 構造方法,這個方法跟到最後,就是將permits許可數設置成 aqs中的state狀態值。
猜想那麼這個值在Semaphore中就可以理解爲一個閾值。臨界區執行的線程數達到閾值了就不允許線程在進入臨界區執行了。
/**
* The synchronization state.
*/
private volatile int state;
acquire
acquire() 方法,一直跟到 .Semaphore .Sync #acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //獲取許可
doAcquireSharedInterruptibly(arg); //將線程加入到鎖池,然後阻塞線程
}
tryAcquireShared方法是aqs的模板方法,被.Semaphore .NonfairSync類重寫,以下是重寫方法。
此方法通過自旋+cas將state減一,然後返回剩餘的許可數量。如果剩餘的許可數量小於0說明沒有許可了,就會調用doAcquireSharedInterruptibly方法
final int nonfairTryAcquireShared(int acquires) {
//自旋,不斷的取值,不斷的cas,直到將state成功減一。
for (;;) {
int available = getState(); //取出aqs的狀態屬性state,該值在Semaphore初始化的時候指定的閾值(許可數量)
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining)) //cas操作
return remaining;
}
}
doAcquireSharedInterruptibly方法是將線程加入到鎖池,然後阻塞線程
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //將線程加入鎖池
boolean failed = true;
try {
for (;;) {
//1.取出node的前置節點
final Node p = node.predecessor();
//2.然後判斷這個node的前置節點是不是aqs中的頭節點,如果是頭節點說明馬上就到當前這個node節點了,所以需要在嘗試獲取一下狀態(state)值(許可數量),因爲在這個過程中很有可能aqs那個頭節點已經release了。
if (p == head) {
int r = tryAcquireShared(arg); //3.如果獲取到了許可那麼就不需要將線程阻塞了
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//如果不符合第一個if判斷,再判斷是否需要掛起等待,如果需要阻塞線程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
release() 方法