最近マルチスレッディングに携わっていますが、乾いた記事を読んで転載しました。
元のブログ:https : //www.cnblogs.com/tong-yuan/p/Semaphore.html
問題
(1)セマフォとは?
(2)セマフォの特徴は何ですか?
(3)セマフォは通常どのようなシナリオで使用されますか?
(4)セマフォのライセンス数を動的に増減できますか?
(5)セマフォはどのように電流制限を実現しますか?
前書き
セマフォ、セマフォ、一連の許可(許可)を保存します。acquire()を呼び出すたびに許可が消費され、release()を呼び出すたびに許可が返されます。
特性
セマフォは通常、同時に共有リソースへのアクセス数を制限するために使用されます。これは、現在の制限と呼ばれます。
Javaでセマフォを実装する方法を学びましょう。
クラス構造
セマフォには、AQSを実装するシンクロナイザSyncと、その2つのサブクラスであるFairSyncとNonFairSyncが含まれています。これは、セマフォもフェアモードとアンフェアモードを区別することを示しています。
ソースコード分析
ReentrantLockとReentrantReadWriteLockの以前の分析に基づくと、この記事は比較的単純です。前述のメソッドの一部は直接スキップされます。興味のある方は、記事の下部に移動して前の記事を表示できます。
内部クラスの同期
// java.util.concurrent.Semaphore.Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 构造方法,传入许可次数,放入state中
Sync(int permits) {
setState(permits);
}
// 获取许可次数
final int getPermits() {
return getState();
}
// 非公平模式尝试获取许可
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 看看还有几个许可
int available = getState();
// 减去这次需要获取的许可还剩下几个许可
int remaining = available - acquires;
// 如果剩余许可小于0了则直接返回
// 如果剩余许可不小于0,则尝试原子更新state的值,成功了返回剩余许可
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 释放许可
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");
// 如果原子更新state的值成功,就说明释放许可成功,则返回true
if (compareAndSetState(current, next))
return true;
}
}
// 减少许可
final void reducePermits(int reductions) {
for (;;) {
// 看看还有几个许可
int current = getState();
// 减去将要减少的许可
int next = current - reductions;
// 检测举出
if (next > current) // underflow
throw new Error("Permit count underflow");
// 原子更新state的值,成功了返回true
if (compareAndSetState(current, next))
return;
}
}
// 销毁许可
final int drainPermits() {
for (;;) {
// 看看还有几个许可
int current = getState();
// 如果为0,直接返回
// 如果不为0,把state原子更新为0
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
Syncのいくつかの実装方法により、次の情報を取得しました。
(1)メソッドの構築時に許可が渡されます。
(2)権限は状態変数stateに格納されます。
(3)ライセンスを取得しようとすると、状態の値が1減少します。
(4)状態の値が0の場合、これ以上の許可は取得できません。
(5)ライセンスが解放されると、stateの値が1増加します。
(6)ライセンスの数は動的に変更できます。
内部クラスNonfairSync
// java.util.concurrent.Semaphore.NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
// 构造方法,调用父类的构造方法
NonfairSync(int permits) {
super(permits);
}
// 尝试获取许可,调用父类的nonfairTryAcquireShared()方法
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
非フェアモードでは、親クラスのnonfairTryAcquireShared()を直接呼び出して、アクセス許可の取得を試みます。
内部クラスFairSync
// java.util.concurrent.Semaphore.FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
// 构造方法,调用父类的构造方法
FairSync(int permits) {
super(permits);
}
// 尝试获取许可
protected int tryAcquireShared(int acquires) {
for (;;) {
// 公平模式需要检测是否前面有排队的
// 如果有排队的直接返回失败
if (hasQueuedPredecessors())
return -1;
// 没有排队的再尝试更新state的值
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
フェアモードでは、最初にその前にキューがあるかどうかを確認します。キューがある場合、権限の取得とキューへの入力に失敗します。それ以外の場合は、状態の値をアトミックに更新しようとします。
工法
// 构造方法,创建时要传入许可次数,默认使用非公平模式
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 构造方法,需要传入许可次数,及是否公平模式
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
セマフォを作成するときは、ライセンスの数を渡す必要があります。
セマフォもデフォルトでは不公平モードですが、2番目のコンストラクターを呼び出して、それを公平モードとして宣言できます。
以下の方法は、以前の内容を検討した後、比較的単純なようです。TongGeは、Semaphoreによってサポートされている一部の関数のみをリストしています。
以下のメソッドはすべて、不公平モードについて説明されています。
acquire()メソッド
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
ライセンスを取得するためのデフォルトの方法は中断可能であり、ライセンスの取得に失敗すると、AQSキューに入ります。
acquireUninterruptibly()メソッド
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
中断されない方法でライセンスを取得しますライセンスの取得に失敗すると、AQSキューに入ります。
tryAcquire()メソッド
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
ライセンスの取得を試み、同期のアンフェアモードを使用してライセンスメソッドの取得を試み、ライセンスが取得されたかどうかに関係なく戻ります。1回だけ試行して、キューに入れません。
tryAcquire(long timeout、TimeUnit unit)メソッド
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
ライセンスの取得を試み、最初にライセンスの取得を試みます。失敗した場合、タイムアウト期間待機します。この期間内にライセンスが取得されない場合はfalseを返し、それ以外の場合はtrueを返します。
release()メソッド
public void release() {
sync.releaseShared(1);
}
ライセンスを解放するライセンスを解放すると、stateの値が1増加し、ライセンスの取得を待機している次のスレッドが呼び起こされます。
acquire(int permit)メソッド
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
一度に複数のライセンスを取得し、中断することができます。
acquireUninterruptibly(int permit)メソッド
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
無停止で、一度に複数のライセンスを取得します。
tryAcquire(int permit)メソッド
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
一度に複数のライセンスを取得するようにしてください。
tryAcquire(int permit、long timeout、TimeUnit unit)メソッド
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
複数のライセンスを取得して、タイムアウト期間を待ちます。この期間中にライセンスが取得されない場合はfalseを返し、それ以外の場合はtrueを返します。
release(int permit)メソッド
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
複数の許可が一度に解放される場合、stateの値はそれに応じて許可の数を増やします。
availablePermits()メソッド
public int availablePermits() {
return sync.getPermits();
}
利用可能なライセンスの数を取得します。
drainPermits()メソッド
public int drainPermits() {
return sync.drainPermits();
}
現在使用可能なライセンスの数を破棄しても、取得したライセンスには影響がなく、残りのすべてのライセンスは破棄されます。
reducePermits(int reduction)メソッド
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
許可の数を減らします。
総括する
(1)セマフォは、セマフォとも呼ばれ、通常、同時に共有リソースへのアクセスを制御するために使用されます。つまり、現在の制限シナリオです。
(2)セマフォの内部実装は、AQSの共有ロックに基づいています。
(3)セマフォは、初期化時にパーミットの数を指定する必要があり、パーミットの数は状態に格納されます。
(4)ライセンスを取得すると、状態値が1減ります。
(5)ライセンスが解放されると、状態値が1増加します。
(6)N許可は動的に削減できます。
(7)n個のライセンスを動的に追加できますか?
イースターエッグ
(1)n個のライセンスを動的に追加するにはどうすればよいですか?
回答:release(int permit)を呼び出すだけです。ライセンスが解放されると、stateの値はそれに応じて増加します。解放ライセンスのソースコードを振り返ると、それがReentrantLockの解放ロックとは多少異なることがわかります。セマフォは、ライセンスを解放するときに現在のスレッドがライセンスを取得したかどうかを確認しません。したがって、リリースライセンスメソッドを呼び出して、いくつかのライセンスを動的に追加できます。
(2)電流制限を実現するにはどうすればよいですか?
回答:電流制限、つまりトラフィックが突然増加した場合、上位層はダウンストリームサービスへの突然の大きなトラフィックの影響を制限できなければなりません。分散システムでは、電流制限は一般にゲートウェイレイヤーで行われます。もちろん、個々の機能で使用することもできます。スパイクシナリオなどのフローを単純に制限します。スパイクする必要がある製品が10個しかない場合、サービス自体は同時に100リクエストのみを制限でき、他のすべてのリクエストは無効化されるため、サービスのプレッシャーはそれほど大きくなりません。
セマフォを使用すると、この関数の電流を直接制限できます。コードの実装は次のとおりです。
public class SemaphoreTest {
public static final Semaphore SEMAPHORE = new Semaphore(100);
public static final AtomicInteger failCount = new AtomicInteger(0);
public static final AtomicInteger successCount = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(()->seckill()).start();
}
}
public static boolean seckill() {
if (!SEMAPHORE.tryAcquire()) {
System.out.println("no permits, count="+failCount.incrementAndGet());
return false;
}
try {
// 处理业务逻辑
Thread.sleep(2000);
System.out.println("seckill success, count="+successCount.incrementAndGet());
} catch (InterruptedException e) {
// todo 处理异常
e.printStackTrace();
} finally {
SEMAPHORE.release();
}
return true;
}
}