JUCのセマフォ

簡単な紹介

セマフォは、複数のカウンタの共有リソースへのアクセスを制御するセマフォあり、かつ本質的であるたCountDownLatch、として「共有ロック。」

カウンティングセマフォ。概念的には、セマフォは、権限のセットを保持しています。

如有必要,在许可可用前会阻塞每一个 acquire,然后再获取该许可。
每个 release 添加一个许可,从而可能释放一个正在阻塞的获取者。
复制代码

しかし、実際の許可オブジェクトは、使用可能なライセンスのセマフォだけ数がカウントされていない、それに応じて行動します。

セマフォは、一般的に、特定のリソースにアクセスできるスレッド数を制限するために使用される(物理的または論理的)です。

ここでは、セマフォの駐車場を説明するために簡単な例があります。

  1. 簡単にするために、我々はそれだけで5駐車スペースの駐車場を想定しています。すべてすべての空全く車両の駐車スペースが始まりませんし、その後3台の車の到着を持って、十分な駐車スペース、駐車場の手配はその後3再び、行く、今回2つだけの駐車スペース、すべての2つだけが停止しているため、必見の残りは無料の駐車スペースまで外で待っていました。もちろん、それぞれの後に、私たちは外を指定する必要があります。そこに車を駐車する場合、空席があり、その後に行くために車の手配(選択のメカニズムに依存するためにどの車、公正または不当です)。

  2. プログラム、等価駐車セマフォセマフォの観点から請求項5のライセンス番号、スレッドに対する車両。車に関しては、ライセンス番号はマイナス1になります。駐車場は何の駐車スペースでない場合(ライセンス番号は== 0)、他の車両は、外待機する必要があります。あなたが駐車場の外に車を運転している場合は、ライセンス番号+ 1は、その後、車に置きます。

  3. セマフォセマフォは負でない整数(> = 1)です。スレッドが共有リソースにアクセスしようとすると、それは最初のセマフォを取得する必要があります。ときセマフォ> 0、リソースとセマフォの獲得 - 1。Sのemaphore値が= 0の場合、すべての共有リソースが完全に別のスレッドによって占有されている場合は、スレッドがリソースを解放するために、他のスレッドを待つ必要があります。場合は、スレッドのリリースリソース、セマフォは1です。

分析を実現

java.util.concurrent.Semaphore構造は、以下に示すように:

図から分かるように、内部ロックセマフォは、SYNC連続AQSは(再びAQSの重要性が示されている)、請求フェア(FairSync)と不当ロック(NonfairSync)、内部クラス継承Syncを含みます。

セマフォは、2つのコンストラクタを提供します。

Semaphore(int permits) :创建具有给定的许可数和非公平的公平设置的 Semaphore 。
Semaphore(int permits, boolean fair) :创建具有给定的许可数和给定的公平设置的 Semaphore 。
复制代码

以下を達成するために:

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

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

セマフォは、デフォルト以外の公正なロックを選択します。

セマフォセマフォ= 1は、それがミューテックスとして使用することができた場合。これは0に相当し、それは述べて:

  1. = 1のとき、他のスレッドが取得されてもよいです。
  2. とき= 0、排他的に、つまり、他のスレッドが待機しなければなりません。

セマフォの実装コードの構造等ReentrantLockの。

セマフォ取得

セマフォは、ライセンスを取得するために#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型引数)メソッドは、#tryAcquireShared(int型引数)法と呼ばれます。そして#tryAcquireShared(int型引数)メソッド、サブクラスによって実装されています。私たちは非株式モードを選択した場合セマフォについては、#tryAcquireSharedのNonfairSync(int型引数)メソッドが呼び出されるか#tryAcquireShared(int型引数)メソッドの呼び出しFairSync。#tryAcquireShared(INT引数)メソッド戻り<0の場合は以下のように、それは、不十分セマフォセマフォでブロッキング行うのを待ってブロックします。

// AQS.java
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            /**
             * 对于 Semaphore 而言,如果 tryAcquireShared 返回小于 0 时,则会阻塞等待。
             */
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

また、セマフォは、これは、なぜあなたはAQSを使用する場合、状態の代表者が利用可能なライセンスの残りの数ではなく、すでに使用中のライセンスの数。状態、同時場合、この操作は、安全問題のスレッドがある - 私たちは、状態は(int型引数)=ライセンスの元の数の復帰後、結果は#tryAcquireShared、既に使用中のライセンスの数を表していることを前提としています。だから、国家の代表者が利用可能なライセンスの残りの数ではなく、すでに使用中のライセンスの数。

FairSync方法不公平実装コードは次の通りであります:

// FairSync.java
@Override
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;
    }
}
复制代码

#hasQueuedPredecessors()メソッドでは、スレッドが配置されている公正なロックを達成するために、カラムヘッドCLHキューを決定します。次のようにNonfairSync方法は、不公平な状況を実装します:

// NonfairSync.java
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

// Sync.java
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
复制代码

非公正な用語については、それが現在のスレッドがCLH同期キューの列ヘッダーに位置しているかどうかを確認しないので、比較的簡単になりますので。

セマフォが解放されます

ライセンスを取得し、あなたが出て実行する必要がリリースされたときに、セマフォがライセンスを解放する#release()メソッドを提供しています。コードは以下の通りであります:

public void release() {
    sync.releaseShared(1);
}
复制代码

#releaseSharedのAQS内部コール(int型引数)方法、同期のリリース。

// AQS.java
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

releaseShared(int型引数)メソッドは、#tryReleaseShared(int型引数)方法、同期のリリースの同期セマフォ内部クラスと呼ばれます。

// Sync.java
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;
    }
}
复制代码

メソッドがtrueを返した場合、リリースを表し、場合同期ステータスは成功し、#releaseShared(int型引数)メソッドので、#doReleaseShared()メソッド、許可セマフォのスレッドを待っているウェイクブロックを呼び出します。

アプリケーション例

ここでは、例として、駐車場があります。

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();
        }
    }
}
复制代码

結果は以下の通りであります:

おすすめ

転載: juejin.im/post/5d8b184151882534b02f17d0