11-J.U.C之併發工具類:Semphore

1. 簡介

       信號量Semaphore是一個控制訪問多個共享資源的計數器,和CountDownLatch一樣,其本質上是一個“共享鎖”。

       Semaphore,在API是這麼介紹的:

       一個計數信號量。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個acquire(),然後再獲取該許可。每個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore只對可用許可的號碼進行計數,並採取相應的行動

       Semaphore通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目

       下面我們就一個停車場的簡單例子來闡述Semaphore

       爲了簡單起見我們假設停車場僅有5個停車位,一開始停車場沒有車輛所有車位全部空着,然後先後到來三輛車,停車場車位夠,安排進去停車,然後又來三輛,這個時候由於只有兩個停車位,所有隻能停兩輛,其餘一輛必須在外面候着,直到停車場有空車位,當然以後每來一輛都需要在外面候着。當停車場有車開出去,裏面有空位了,則安排一輛車進去(至於是哪輛要看選擇的機制是公平還是非公平)。

       從程序角度看,停車場就相當於信號量Semaphore,其中許可數爲5,車輛就相對線程。當來一輛車時,許可數就會減1,當停車場沒有車位了(許可數 == 0 ),其他來的車輛需要在外面等候着。如果有一輛車開出停車場,許可數 + 1,然後放進來一輛車。

       信號量Semaphore是一個非負整數(>=0)。當一個線程想要訪問某個共享資源時,它必須要先獲取Semaphore,當Semaphore >0時,獲取該資源並使Semaphore – 1。如果Semaphore = 0,則表示全部的共享資源已經被其他線程全部佔用,線程必須要等待其他線程釋放資源。當線程釋放資源時,Semaphore+1,並喚醒等待的線程。

2. 實現分析

       Semaphore結構如下:

在這裏插入圖片描述
       從上圖可以看出Semaphore內部包含公平鎖(FairSync)和非公平鎖(NonfairSync),繼承內部類Sync,其中Sync繼承AQS

       Semaphore提供了兩個構造函數:

	public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

	public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

       1、Semaphore(int permits) :創建具有給定的許可數和非公平的公平設置的 Semaphore

       2、Semaphore(int permits, boolean fair) :創建具有給定的許可數和給定的公平設置的 Semaphore

       Semaphore默認選擇非公平鎖。當信號量Semaphore = 1時,它可以當作互斥鎖使用。其中01就相當於它的狀態,當=1時表示其他線程可以獲取,當=0時,排他,即其他線程必須要等待。

2.1 信號量獲取

       Semaphore提供了acquire()方法來獲取一個許可。

	public void acquire() throws InterruptedException {
	    sync.acquireSharedInterruptibly(1);
	}

       內部調用AQS的acquireSharedInterruptibly(int arg),該方法以共享模式獲取同步狀態:

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

       在acquireSharedInterruptibly(int arg)中,tryAcquireShared(int arg)由子類來實現,對於Semaphore而言,如果我們選擇非公平模式,則調用NonfairSync的tryAcquireShared(intarg)方法,否則調用FairSync的tryAcquireShared(int arg)方法。

2.1.1 公平鎖

protected int tryAcquireShared(int acquires) {
    for (;;) {
        //判斷該線程是否位於CLH隊列的列頭
        if (hasQueuedPredecessors())
            return -1;
        //獲取當前的信號量許可
        int available = getState();

        //設置“獲得acquires個信號量許可之後,剩餘的信號量許可數”
        int remaining = available - acquires;

        //CAS設置信號量
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

2.1.2 非公平鎖

       對於非公平而言,因爲它不需要判斷當前線程是否位於CLH同步隊列列頭,所以相對而言會簡單些。

	protected int tryAcquireShared(int acquires) {
	    return nonfairTryAcquireShared(acquires);
	}
	
	final int nonfairTryAcquireShared(int acquires) {
	    for (;;) {
	        int available = getState();
	        int remaining = available - acquires;
	        if (remaining < 0 ||
	            compareAndSetState(available, remaining))
	            return remaining;
	    }
	}

2.2 信號量釋放

       獲取了許可,當用完之後就需要釋放,Semaphore提供release()來釋放許可。

	public void release() {
	    sync.releaseShared(1);
	}

       內部調用AQSreleaseShared(int arg)

	public final boolean releaseShared(int arg) {
	    if (tryReleaseShared(arg)) {
	        doReleaseShared();
	        return true;
	    }
	    return false;
	}

       releaseShared(int arg)調用Semaphore內部類SynctryReleaseShared(int arg)

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        //信號量的許可數 = 當前信號許可數 + 待釋放的信號許可數
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        //設置可獲取的信號許可數爲next
        if (compareAndSetState(current, next))
            return true;
    }
}

3. 應用示例

       我們以停車爲示例:

import java.util.concurrent.Semaphore;

/**
 * @author wangzhao
 * @date 2020/6/4 9:40
 */
public class SemaphoreTest {

    static class Parking{
        //信號量
        private Semaphore semaphore;

        Parking(int count){
            semaphore = new Semaphore(count);
        }

        public void park(){
            try {
                //獲取信號量
                semaphore.acquire();
                long time = (long) (Math.random() * 10);
                System.out.println(Thread.currentThread().getName() + "進入停車場,停車" + time + "秒..." );
                Thread.sleep(time);
                System.out.println(Thread.currentThread().getName() + "開出停車場...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    }


    static class Car extends Thread {
        Parking parking ;

        Car(Parking parking){
            this.parking = parking;
        }

        @Override
        public void run() {
            parking.park();     //進入停車場
        }
    }

    public static void main(String[] args){
        Parking parking = new Parking(3);

        for(int i = 0 ; i < 5 ; i++){
            new Car(parking).start();
        }
    }

}

在這裏插入圖片描述

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