Java併發系列之八Semaphore

前面我已經講解過了CountDownLatch和CyclicBarrier。本篇我們來講解下Semaphore。

Semaphore是指信號量,在計算機的世界裏信號量可以使用在數據競爭的場景中。在生活中交通信號燈可以比作現實世界中的Semaphore。Semaphore的作用就是允許或者禁止。比如說紅燈禁止通行,綠燈允許通行。計算機世界裏的Semaphore會持有多張許可證,舉個例子有10張許可證,假設有20個線程同時請求信用量的許可證,那麼只能是其中十個線程能夠拿到許可證,執行代碼。另外10個線程只能等拿到許可證的線程釋放許可證。舉個生活中的例子。比如在銀行辦理業務,假設大廳有3個窗口。辦理業務的羣衆有10個。那麼每次只能有3個羣衆能獲得叫號的資格(對應計算機拿到許可證),如果有一個羣衆辦理完業務了,那麼窗口才能空餘出來(對應計算機線程釋放許可證),等待的羣衆才能獲得叫號的資格。

我們用代碼來模擬下該場景。假設只有3個窗口,有10個羣衆來辦理業務,每個業務辦理1~5秒鐘不等

public class Bank {
    int[] windows = new int[3];//3個辦事窗口
    final boolean[] busy = new boolean[3];//辦事窗口是否busy
    String[] persons = new String[10];
    Semaphore semaphore = new Semaphore(3);//最多隻有3張許可證

    {
        for (int i = 0; i < 10; i++) {
            persons[i] = "person " + i;
        }
    }

    ExecutorService pool = Executors.newCachedThreadPool();

    public void deal() {
        for (final String person : persons) {
            pool.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();
                        int index = 0;

                        synchronized (busy) {
                            for (int i = 0; i < 3; i++) {
                                if (!busy[i]) {//如果該窗口空閒
                                    index = i;
                                    busy[i] = true;
                                    System.out.println("請 " + person + " 到 " + index + " 號窗口辦理業務");
                                    break;
                                }
                            }
                        }
                        Random random = new Random();
                        int second = 1 + random.nextInt(5);
                        TimeUnit.SECONDS.sleep(second);
                        System.out.println(person + "在"+index+" 窗口辦理完成 費時" + second + "秒");
                        synchronized (busy) {
                            busy[index] = false;
                        }
                    } catch (
                            InterruptedException e
                            )

                    {
                        e.printStackTrace();
                    } finally

                    {
                        semaphore.release();
                    }

                }
            });
        }

    }

    public static void main(String[] args) {
        Bank bank = new Bank();
        bank.deal();
    }
}

輸出結果如下

請 person 0 到 0 號窗口辦理業務
請 person 1 到 1 號窗口辦理業務
請 person 2 到 2 號窗口辦理業務
person 1在1 窗口辦理完成 費時1秒
請 person 3 到 1 號窗口辦理業務
person 0在0 窗口辦理完成 費時3秒
請 person 4 到 0 號窗口辦理業務
person 2在2 窗口辦理完成 費時3秒
請 person 5 到 2 號窗口辦理業務
person 3在1 窗口辦理完成 費時2秒
請 person 6 到 1 號窗口辦理業務
person 4在0 窗口辦理完成 費時3秒
請 person 7 到 0 號窗口辦理業務
person 5在2 窗口辦理完成 費時4秒
請 person 8 到 2 號窗口辦理業務
person 6在1 窗口辦理完成 費時5秒
請 person 9 到 1 號窗口辦理業務
person 8在2 窗口辦理完成 費時2秒
person 7在0 窗口辦理完成 費時4秒
person 9在1 窗口辦理完成 費時3秒

從打印結果我們可以看出,每次最多允許3個人辦理業務,每當窗口空閒出來就會叫號。

接下來我們看看Semaphore源碼

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }
}

Semaphore持有一個Sync對象,該對象是一個AQS。之前的併發系列我們講過AQS。它是同步的核心。而Lock類也是持有Sync對象。我們可以把Semaphore看成是一個Lock。區別是Semaphore沒有lock和unlock方法,取而代之的是acquire 和release方法

public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}
    
public boolean tryAcquire(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.nonfairTryAcquireShared(permits) >= 0;
}

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

看到share字樣,首先這把鎖是共享鎖。每次獲取到了鎖,許可證數量-1,如果許可證數量小於0,則獲取鎖失敗,該線程等待

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

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");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

release方法許可證+1,同時通知等待線程,嘗試獲取許可證

總結下CountDownLatch CyclicBarrier Semaphore的區別

CountDownLatch 適合做彙總相關的工作,比如開啓多個線程爬取數據,在所有線程完成任務後,將數據做彙總。

CyclicBarrier適合於競賽相關的場景,讓多個線程在同一時間點執行任務。

Semaphore適用於資源有限的情況下的流量控制。

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