JAvaハイコンカレントシリーズ-15日目:JUCのSemaphore(信号量)

15376 ワード

JAvaハイコンカレントシリーズ-15日目:JUCのSemaphore(信号量)
オリジナル: 通行人甲Java 通行人甲Java 7月22日
JAva高同時シリーズ第15編記事
Semaphore(信号量)はマルチスレッドコラボレーションのためにより強力な制御方法を提供し,前述の論文ではsynchronizedと再ロックReentrantLockを学び,この2つのロックは一度に1つのスレッドのみが1つのリソースにアクセスできるが,信号量は特定のリソースにアクセスできるスレッドがどれだけあるかを制御できる.
Semaphore共通シーン:ストリーム制限
例を挙げます.
例えば駐車場があって、5つの空席があって、入り口に警備員がいて、手の中の5つの鍵はそれぞれ5つの駐車スペースの上の鍵に対応して、1台の車が来て、警備員は運転手に鍵をあげて、それから中に入って対応する駐車スペースを見つけて止まって、出かける時運転手は鍵を警備員に返します.駐車場の商売は比較的に良くて、同時に100両の車が来て、警備員の手の中で5つの鍵しかなくて、同時に5台の車を置いて入るしかなくて、他の車は待つしかなくて、誰かが鍵を警備員に返してから、他の車を入れることができます.
上記の例では、警備員はSemaphoreに相当し、車の鍵は許可証に相当し、車はスレッドに相当する.
Semaphoreの主な方法
Semaphore(int permits):信号量を作成するためのライセンス数を表す構築方法
Semaphore(int permits,boolean fair):fairがtrueに等しい場合、所定のライセンス数を有するカウント信号量を作成し、公平な信号量に設定する方法
void acquire()throws InterruptedException:この信号量から1つのライセンスを取得する前にスレッドがブロックされ、1台の車が1つの駐車スペースを占めていることに相当します.このメソッドはスレッドの中断に応答し、スレッドを呼び出すinterruptメソッドを表し、InterruptedException異常を放出します.
 
void acquire(int permits) throws InterruptedException :acquire()メソッドと同様に、パラメータはライセンスを取得する必要がある数を示します.例えば、大きなトラックが駐車場に入る場合、車が大きいので、駐車スペースを3つ申請しなければなりません.
 
void acquireUninterruptibly(int permits) :acquire(int permits)メソッドと同様ですが、スレッド中断に応答しません.
 
boolean tryAcquire():成功するかどうかにかかわらず、1つのライセンスを取得しようとします.trueは取得に成功し、falseは取得に失敗しました.
boolean tryAcquire(int permits):およびtryAcquire()は、permits個のライセンスを取得しようとしていることを示します.
boolean tryAcquire(long timeout,TimeUnit unit)throws InterruptedException:指定した時間内に1つのライセンスを取得しようとしたが、取得に成功してtrueに戻り、指定した時間が過ぎてもライセンスを取得できずfalseに戻る
boolean tryAcquire(int permits,long timeout,TimeUnit unit)throws InterruptedException:tryAcquire(long timeout,TimeUnit unit)と同様にpermitsパラメータが1つ増え、permits個のライセンスを取得しようとしていることを示します
void release():駐車場を出るときに鍵を警備員に返すことに相当する信号量に戻す許可を発行する
void release(int n):n個のライセンスを解放する
int availablePermits():現在使用可能なライセンス数
例1:Semaphoreの簡単な使用
package com.itsoku.chat12;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 *      :   Java,   java    (       、     、      、    、    、    ),     !
 */
public class Demo1 {
    static Semaphore semaphore = new Semaphore(2);

    public static class T extends Thread {
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new T("t-" + i).start();
        }
    }
}

出力:
1563715791327,t-0,    !
1563715791327,t-1,    !
1563715794328,t-0,    !
1563715794328,t-5,    !
1563715794328,t-1,    !
1563715794328,t-2,    !
1563715797328,t-2,    !
1563715797328,t-6,    !
1563715797328,t-5,    !
1563715797328,t-3,    !
1563715800329,t-6,    !
1563715800329,t-9,    !
1563715800329,t-3,    !
1563715800329,t-7,    !
1563715803330,t-7,    !
1563715803330,t-8,    !
1563715803330,t-9,    !
1563715803330,t-4,    !
1563715806330,t-8,    !
1563715806330,t-4,    !

 
コード  newSemaphore(2)はライセンス数2の信号量を作成し、各スレッドは1つのライセンスを取得し、同時に2つのスレッドがライセンスを取得することを許可し、出力からも2つのスレッドがライセンスを取得することができ、他のスレッドはライセンスを取得したスレッドがライセンスを解放してから実行する必要があることがわかります.ライセンスを取得するスレッドがブロックされます.  acquire()メソッドでは、ライセンスが取得されるまで続行できません.
例2:ライセンスを取得した後にリリースしない
警備員(Semaphore)は少しぼんやりしていて、運転手が入ったときに鍵を渡して、出たときに返さないし、警備員も何も言わない.結局、他の車は入れなくなった.
次のコードがあります.
package com.itsoku.chat12;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 *      :   Java,   java    (       、     、      、    、    、    ),     !
 */
public class Demo2 {
    static Semaphore semaphore = new Semaphore(2);

    public static class T extends Thread {
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",        :" + semaphore.availablePermits());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new T("t-" + i).start();
        }
    }
}

出力:
1563716603924,t-0,    !
1563716603924,t-1,    !
1563716606925,t-0,    !
1563716606925,t-0,        :0
1563716606925,t-1,    !
1563716606925,t-1,        :0

上のプログラムが実行されてからずっと终わらないで、コードを観察して、コードの中でライセンスを取得した后に、ライセンスのコードを解放していないで、最后に、使用可能なライセンスの数は0で、その他のスレッドはライセンスを取得することができなくて、  semaphore.acquire();で待機しているため、プログラムが終了できません.
例3:正しいポーズを許可解除
例1ではfinallyでロックを解除していますが、問題はありますか?
ロックの取得中に異常が発生し、ロックの取得に失敗した場合、finallyでもライセンスが解放され、最終的にはどうなるのか、ライセンス数が空っぽに増加します.
サンプルコード:
package com.itsoku.chat12;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 *      :   Java,   java    (       、     、      、    、    、    ),     !
 */
public class Demo3 {
    static Semaphore semaphore = new Semaphore(1);

    public static class T extends Thread {
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    ,        :" + semaphore.availablePermits());
                //  100                 
                TimeUnit.SECONDS.sleep(100);
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
            System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",        :" + semaphore.availablePermits());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T t1 = new T("t1");
        t1.start();
        //  1        
        TimeUnit.SECONDS.sleep(1);
        T t2 = new T("t2");
        t2.start();
        //  1         
        TimeUnit.SECONDS.sleep(1);
        T t3 = new T("t3");
        t3.start();        
        // t2 t3              
        t2.interrupt();
        t3.interrupt();
    }
}

出力:
1563717279058,t1,    ,        :0
java.lang.InterruptedException
1563717281060,t2,        :1    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)
1563717281060,t3,        :2    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)    
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)    
	at com.itsoku.chat12.Demo3$T.run(Demo3.java:21)java.lang.InterruptedException    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)    
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)    at com.itsoku.chat12.Demo3$T.run(Demo3.java:21)

プログラム中の信号量ライセンス数は1であり、3つのスレッド取得ライセンスが作成され、スレッドt 1の取得に成功し、100秒休止する.他の2つのスレッドがブロックされています  semaphore.acquire();メソッドでは、スレッドt 2,t 3に割り込み信号をコードで送信し、Semaphoreのacquireのソースコードを見てみましょう.
public void acquire() throws InterruptedException

この方法はスレッド割り込みに応答する、メインスレッドでt 2,t 3に割り込み信号を送信した後、  acquire()メソッドがトリガーされます  InterruptedException異常、t 2、t 3は最終的にライセンスを取得しなかったが、finallyでのライセンスの解放操作を実行し、最後にライセンス数が2になり、ライセンス数が増加した.プログラムでライセンスを解放する方法に問題があります.ロックを解除するには、ライセンスを取得して成功する必要があります.
正しいロック解除方法は、次のとおりです.
package com.itsoku.chat12;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 *      :   Java,   java    (       、     、      、    、    、    ),     !
 */
public class Demo4 {
    static Semaphore semaphore = new Semaphore(1);

    public static class T extends Thread {
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            //                    
            boolean acquireSuccess = false;
            try {
                semaphore.acquire();
                acquireSuccess = true;
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    ,        :" + semaphore.availablePermits());
                //  100                 
                TimeUnit.SECONDS.sleep(5);
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",    !");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (acquireSuccess) {
                    semaphore.release();
                }
            }
            System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",        :" + semaphore.availablePermits());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T t1 = new T("t1");
        t1.start();        
        //  1         
        TimeUnit.SECONDS.sleep(1);
        T t2 = new T("t2");
        t2.start();        
        //  1         
        TimeUnit.SECONDS.sleep(1);
        T t3 = new T("t3");
        t3.start();        
        // t2 t3             
        t2.interrupt();
        t3.interrupt();
    }
}

出力:
1563717751655,t1,    ,        :0
1563717753657,t3,        :0
java.lang.InterruptedException
1563717753657,t2,        :0    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)    
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)    
	at com.itsoku.chat12.Demo4$T.run(Demo4.java:23)java.lang.InterruptedException    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)    
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)    
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)    
	at com.itsoku.chat12.Demo4$T.run(Demo4.java:23)
1563717756656,t1,    !
1563717756656,t1,        :1

プログラムに変数が追加されました  acquireSuccessは、取得ライセンスが成功したかどうかをマークし、finallyでこの変数がtrueであるかどうかに基づいてライセンスを解放するかどうかを決定するために使用される.
例4:所定時間内にライセンスを取得したい場合
運転手さんは駐車場に来て、駐車場がいっぱいになっていることに気づき、外で内部の車が出てから入るしかないのですが、どのくらい待たなければならないのか、自分でもわかりません.彼は10分待ってほしいと思っていますが、まだ入れないなら、ここに駐車しません.
Semaphore内部の2つの方法は、タイムアウトしてライセンスを取得する機能を提供します.
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)        throws InterruptedException 

指定した時間内にライセンスを取得しようとしますが、取得できればtrueに戻り、falseに戻ることはできません.
サンプルコード:
package com.itsoku.chat12;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 *      :   Java,   java    (       、     、      、    、    、    ),     !
 */
public class Demo5 {
    static Semaphore semaphore = new Semaphore(1);

    public static class T extends Thread {
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            //                    
            boolean acquireSuccess = false;
            try {
                //    1      ,      true,    false                
                System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",      ,        :" + semaphore.availablePermits());
                acquireSuccess = semaphore.tryAcquire(1, TimeUnit.SECONDS);
                //                          
                if (acquireSuccess) {
                    System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",      ,        :" + semaphore.availablePermits());
                    //  5                     
                    TimeUnit.SECONDS.sleep(5);
                } else {
                    System.out.println(System.currentTimeMillis() + "," + thread.getName() + ",      ,        :" + semaphore.availablePermits());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (acquireSuccess) {
                    semaphore.release();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T t1 = new T("t1");
        t1.start();
        //  1         
        TimeUnit.SECONDS.sleep(1);
        T t2 = new T("t2");
        t2.start();
        //  1         
        TimeUnit.SECONDS.sleep(1);
        T t3 = new T("t3");
        t3.start();
    }
}

出力:
1563718410202,t1,      ,        :1
1563718410202,t1,      ,        :0
1563718411203,t2,      ,        :0
1563718412203,t3,      ,        :0
1563718412204,t2,      ,        :0
1563718413204,t3,      ,        :0

コード内のライセンス数は1です.  semaphore.tryAcquire(1,TimeUnit.SECONDS);:1秒以内にライセンスを取得しようとすると、取得に成功したらすぐにtrueに戻り、1秒を超えても取得できないことを示し、falseに戻ります.スレッドt 1はライセンス取得に成功し、その後5秒間休眠し、出力からt 2とt 3がいずれも1秒試行し、取得に失敗したことがわかる.
その他の使用方法
  • Semaphoreデフォルトで作成された非公平な信号量とはどういう意味ですか?これは公平と非公平にかかわる.例を挙げると、5つの駐車スペースで、5つの車両が入ることができ、100台の車が来て、5台しか入ることができず、他の95は外で並んで待っています.中からちょうど1台が出てきたので、ちょうど10台が来ました.この10台は他の95台の前に直接割り込んで行きますか、それとも95台の後ろに並んでいますか.新しく来た人を列に並ばせるのは公平を表し、直接列に割り込んで最初の人を争うのは不公平を表す.駐車場には、もっと並んだほうがいいに違いない.しかし、信号量にとって不公平な効率はもっと高いので、デフォルトは不公平です.
  • 以下のSemaphoreのソースコードを読むことをお勧めします.よく使う方法についてよく知っています.覚えておく必要はありません.使うときも簡単に検索すればいいです.
  • メソッドには  throwsInterruptedException宣言は、この方法がスレッド中断信号に応答することを示しています.どういう意味ですか.呼び出しスレッドを表す  interrupt()メソッドの後、これらのメソッドがトリガーされます.  InterruptedException異常で、これらの方法がブロックされている場合でも、すぐに戻り、放出されます.  InterruptedException異常、スレッド割り込み信号もクリアされます.