Semaphore信號量 Semaphore信號量 acquire

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() 方法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章