序文
簡単な紹介
セマフォ中国がセマフォを言って、それがReentrantLockのは異なり、ReentrantLockのであることを、一つだけのスレッドが、セマフォが共有されているリソースを所有することができ、排他的である、それは複数のスレッドが同時に共有モードでAQSを達成するために、リソースを持っていることができ、以前AQS分析記事で、私はまた、共有ロックを説明するためにセマフォを使用します
実際には、我々はキューに必要なのですか、なぜ、このような海釣りなどのディナーに、私たちの熱い小さなレストラン、、、多くの人が食べるので、そこだけ収容することができますので、場所は十分ではありません、私たちが並んしようとしている、一部の人々は、新しいを持っているために外食します人々は、同じ道を行きます
使用
一部の人々はまだ使用されていないことがあり、次の一般的なプロセスは、その後、小さなデモを書き、彼の使用について説明し
、コードの下を見て:
/**
* @ClassName SemaphoreDemo
* @Auther burgxun
* @Description: 使用信号量的Demo
* @Date 2020/4/3 13:53
**/
public class SemaphoreDemo {
public static void PrintLog(String logContent) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm.ss.SSS");
System.out.println(String.format("%s : %s", simpleDateFormat.format(new Date()), logContent));
}
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(10);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
PrintLog("Thread1 starting ......");
semaphore.acquire(5);
PrintLog("Thread1 get permits success");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
PrintLog("thread1 release permits success");
semaphore.release(5);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
PrintLog("Thread2 starting ......");
semaphore.acquire(5);
PrintLog("Thread2 get permits success");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
PrintLog("thread2 release permits success");
semaphore.release(5);
}
}
});
thread1.start();
thread2.start();
try {
semaphore.acquire(5);
PrintLog("Main thread get permits success");
Thread.sleep(3000);
} finally {
PrintLog("Main thread release permits");
semaphore.release(5);
}
}
}
上記のコードのロジックは、許可証10のためのセマフォを設定し、二つの新しいスレッドを開くためのコードを、だけでなく、メインスレッドでも5社の許可を取得しながら、5つの許可は、セマフォを取得することは非常に簡単な答えです
私たちは結果を見てみましょう:
2020-04-08 11:53.40.539 : Thread1 starting ......
2020-04-08 11:53.40.539 : Main thread get permits success
2020-04-08 11:53.40.539 : Thread2 starting ......
2020-04-08 11:53.40.544 : Thread1 get permits success
2020-04-08 11:53.43.543 : Main thread release permits
2020-04-08 11:53.43.543 : Thread2 get permits success
2020-04-08 11:53.43.544 : thread1 release permits success
2020-04-08 11:53.46.543 : thread2 release permits success
許可証を取得するためにしながら、結果からは、プログラムの開始時間、スレッド1とスレッド2だけでなく、メインスレッドを見ることができますが、セマフォの許可の合計では10であるため、打ち上げが、ブロックされますが、そのメインスレッドとスレッド1は、後に、スレッド2に到達したときここでは、信号の量、AQSへのSyncQueueは、MainThreadまたはスレッド1の実行が完了したときに、許可証のリリース後、スレッド2が新たな買収から戻ってきてキューを遮断から復帰し、後者は継続します!
ソースコード解析
私はあなたがセマフォのいくつかの知識を持っていると信じて小さなデモの上お読みになった後、我々はそれがセマフォ構造図で当社初見、達成する方法で、ソースコードになります:
図のクラス構造。
グラフから、我々は、このクラスの施設を見ることができると我々はほとんどReentrantLockのを見る前に、静的同期抽象クラスであり、2つの継承された同期のクラスがあり、クラスが公正FairSync、別の非公正NonfairSyncある
フェアについてそしてReentrantLockのIの終わりに非株式オプションは、ここで言っているのではない、いくつかの精巧に作られている
コードは、それを達成するために見えること!
同期
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* m默认的构造函数 设置AQS同步器的State值
*/
Sync(int permits) {
setState(permits);
}
/**
* 获取同步器的状态值 State 这个就是获取设置的许可数量
*/
final int getPermits() {
return getState();
}
/**
* 实现共享模式下非公平方法的获取资源
*/
final int nonfairTryAcquireShared(int acquires) {
for (; ; ) {//自旋
int available = getState();//当前的同步器的状态值
int remaining = available - acquires;//本次用完 还剩下几个permits值
// 如果剩余小于0或者CAS 成功 就返回 后面利用这个方法的时候 会判断返回值的
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/**
* 共享模式下的尝试释放资源
*/
protected final boolean tryReleaseShared(int releases) {
for (; ; ) {
int current = getState();
int next = current + releases;//用完的permits 就要加回去
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))//CAS 修改AQS的State 由于可能存在多个线程同时操作的State
// 所以要使用CAS操作 失败了就继续循环,CAS成功就返回
return true;
}
}
/**
* 根据参数reductions 去减少信号量
*/
final void reducePermits(int reductions) {
for (; ; ) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
/**
* 清空所有信号量 并返回清空的数量
* 这边我看很多文章里面的人只是翻译了英文 获取信号量 但是这个里面有个清空的操作
*/
final int drainPermits() {
for (; ; ) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
多くはないが、非公正バージョンの許可へのアクセスを提供し、一般的な議論されているだけでなく、リリースの統一された方法は、内部のデフォルトを許可し、残りはセマフォの取得方法のgetPermitsあるSyncメソッド、許可および現在の方法の数を減らしますreducePermits、最終的には、明確なセマフォ方法のdrainPermits、drainPermitsは注釈として翻訳され、この方法多くの記事は、ライセンスを取得して返すために翻訳されますが、この方法は実際にはセマフォの主な仕事である場合の動作内部のセマフォをクリアすることである第一ので、これは、無味方法を提供なぜ、人々は不思議に思うだろうハハ、ここでは考慮にマルチスレッドの同時実行を取って、ここではCAS操作は更新のみを使用することができ、還元はそれ好きではないreducePermitsを使用し、その後getPermitsで取得!コードの各行は、その存在の意味を持ち、人のように、合理的な存在です!
NonfairSync
/**
* 非公平版本
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
このクラスは、本当に何も言うことはありません、トーン同期の実装があります
FairSync
/**
* 公平版本
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (; ; ) {//这边为什么要用自旋 主要是因为当前版本是共享模式 可能会多个线程同事操作State 导致当前的CAS 操作失败 所以要做重试
if (hasQueuedPredecessors())//如果当前的SyncQueue中还有等待线程 那就直接返回-1 不让当前线程获取资源
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
公正かつ同一の不当な方法の初期化方法は、差が差が大きくないが、トーンの同期は、2つの方法の実現は、あるこのtryAcquireShared、彼ら自身の実現を達成する方法であるが、ロジックが実行されますない同じ、主にhasQueuedPredecessorsリソースへのアクセスを取得していることです。この方法で、公正かつ不公平との違いは、私が説明する必要がReentrantLockの上で、あるスレッドSyncQueue AQS、で待っている、と等価でありフェアは、その後、あなたがキュー待ち、全員のに一つずつではなく、1つのアップは、私は条件は、私が得れば、人々は、後のを待つことはありませんが存在し気にしないことを公共のgetの背面にはあまり時間を取得する場合と同様に、私は、キューをジャンプ即時入居!
セマフォコンストラクタ
セマフォは、コンストラクタを持っています
- セマフォ(int型の許可)これはデフォルトコンストラクタ、である、非株式の総数は、セマフォライセンスを許可します
- セマフォ(int型の許可、ブール公正)と、この違いは、あなたが上記のバージョンを設定することができるということです本当の実現FairSyncの偽がNonfairSyncです
セマフォメンバメソッド
得ます
方法 | 割り込み応答するかどうか | ブロックされています |
---|---|---|
取得() | それはあります | それはあります |
取得(int型の許可) | それはあります | それはあります |
acquireUninterruptibly() | ノー | それはあります |
acquireUninterruptibly(int型の許可) | ノー | それはあります |
tryAcquire() | ノー | ノー |
tryAcquire(int型の許可) | ノー | ノー |
tryAcquire(長いタイムアウト、TimeUnitでユニット) | それはあります | (時間制御)されます |
tryAcquire(int型の許可、長いタイムアウト、TimeUnitでユニット) | それはあります | (時間制御)されます |
上記の方法は〜私はNengkanmingbaiまた、使用セマフォ許可をまとめ、すべての権利もノートを見て名前や時間を見て覚えて、するべきことを得ることです
チューンコアメソッドの取得も
/**
* Semaphore中
* 获取资源
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* AbstractQueuedSynchronizer 中
* 共享模式下的 获取资源 可以响应中断
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//检测下 中断标识符 如果发生了中断 就抛出中断异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//尝试获取资源 如果返回值小于0 说明当前的同步器里面的值 不够当前获取 就进入排队
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//以共享模式加入到阻塞队列中 这里addWaiter和独占锁加锁使用的是同一个方法 不清楚的 可以看之前的文章
final Node node = addWaiter(Node.SHARED);// 返回成功加入队尾的节点
boolean failed = true;//标识是否获取资源失败
try {
for (; ; ) {//自旋
final Node p = node.predecessor();// 获取当前节点的前置节点
if (p == head) {// 如果前置节点是head 那就去尝试获取资源,因为可能head已经释放了资源
int r = tryAcquireShared(arg);
if (r >= 0) {// 如果获取成功且大于等于0,意味这资源还有剩余,可唤醒其余线程获取
setHeadAndPropagate(node, r);// 这边方法就是和独占锁处理不一样地放 我们可以重点去看下 其余的流程是一样的
p.next = null; // help GC
failed = false;
return;
}
}
/*下面的方法和独占锁的是一样的 在第一篇文章中已经解读过,小伙伴们如果不清楚 可以去看下
有区别的地方就是对中断的处理这边是直接抛出中断异常,独占锁处理是返回标记是否中断 让上一层处理中断
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 更新prev节点状态 ,并根据prev节点状态判断是否自己当前线程需要阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;// node的prev节点的状态
if (ws == Node.SIGNAL) // 如果SIGNAL 就返回true 就会执行到parkAndCheckInterrupt方法里面
return true;
/*
* 如果ws 大于0 这里是只能为1,如果是1说明线程已经取消,相当于无效节点
* 者说明 当前node 节点加入到了一个无效节点后面,那这个必须处理一下
node.prev = pred = pred.prev
* 这个操作 我们拆解下来看,下看 pred = pred.prev这个的意思是把prev节点的prev*节点 赋值给prev节点
*后面再看 node.prev = pred 联合 刚才的赋值 这个的意思就是把prev节点的prev节点和node关联起来,
*原因我上面也说了因为pre节点线程取消了,所以node节点不能指向pre节点 只能一个一个的往前找,
*找到waitStatus 小于或者等于0的结束循环最后再把找到的pre节点执行node节点 ,这样就跳过了所有无效的节点
*/
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
*这边的操作就是把pred 节点的状态设置为SIGNAL,这样返回false 这样可以返回到上面的自旋中
*再次执行一次,如果还是获取不到锁,那么又回到当前的shouldParkAfterFailedAcquire方法 执行到方法最上面的判断
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 阻塞当前线程,并在恢复后坚持是否发送了中断
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
/*
* 这边线程阻塞,只有2中方式唤醒当前线程,
* 一种方式就是 当前线程发生中断
* 另外一个情况就是 资源释放的时候会调unpark 方法 唤醒当前的线程 这个会在下一篇会讲到
*/
LockSupport.park(this);
return Thread.interrupted();//检查线程在阻塞过程中 是否发生了中断
}
これらの方法の中で実現の非株式および株式リリースバージョンの実現をオーバーライドするサブクラスを説明したtryAcquireSharedされています!
実装を通じて、そのうち〜も詳細に説明しAQSの記事の前に、AQSに以前の記事を見て不明確です
以下は、全体のプロセスのコールグラフで次のようになります。
解除
/**
* 释放资源
*/
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
同期解除方法はreleaseSharedが実際に内部メソッドreleaseShared AQSを呼び出し、呼び出し、
/**
* AbstractQueuedSynchronizer 中
* 共享版本的 释放资源
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//tryReleaseShared 还是和之前的套路一样 子类去重写的
doReleaseShared();//是不是很熟悉
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/**
* 共享模式下的释放资源
*/
private void doReleaseShared() {
for (; ; ) {
Node h = head;
if (h != null && h != tail) {// 这个head!=tail 说明阻塞队列中至少2个节点 不然也没必要去传播唤醒 如果就自己一个节点 就算资源条件满足 还换个谁呢?
int ws = h.waitStatus;// head 节点状态SIGNAL
if (ws == Node.SIGNAL) {// 如果head状态是
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);//是和独占锁释放用的同样的方法 唤醒的是下一个节点 前面的文章有分析到
} else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; //这边设置为-3 是为了唤醒的传播 也就是满足上一个方法有判断waitStatus 小于0
}
if (h == head)
break;
}
}
/**
* 唤醒等待线程去获取资源
*/
private void unparkSuccessor(Node node) {
/*
* 这边判断了下如果当前节点状态小于0,更新这边的节点状态的0,是为了防止多次释放的时候 会多次唤醒,
* 因为上面的方法有个判断waitStatus不等0才会执行到这个方法里面
*/
int ws = node.waitStatus;//这边的弄得节点就是 要释放的节点 也就是当前队列的头节点
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 如果当前节点的next 节点 不存在或者waitStatus 大于0 说明next节点的线程已取消
if (s == null || s.waitStatus > 0) {
s = null;
//这个循环就是 从尾部节点开始往前找,找到离node节点也就是当前释放节点最近的一个非取消的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
/*
*一开始觉得 这行判断null有点多余 因为上面去for 循环去找s 的时候 已经判断了不等于null
*才可以进下面的循环赋值的 后来一想 不对,你们猜为什么?
*因为 可能在循环的过程中t在赋值给s后,继续循环 虽然条件不满足,
*但是这个时候已经比修改成null 了 我是这么想的哈~不知道对不对~
*/
if (s != null)
LockSupport.unpark(s.thread);// 这边就是唤醒当前线程去获取资源,
}
インサイドdoReleaseSharedの具体的な方法私は、詳細な概要を行っているロックAQSの記事を共有する前に、ここで再び、私は詳細には触れません!
AQSは単にメソッドが例外をスロー書い達成しなかった以前と同じtryReleaseShared方法またはルーチンは、サブクラスは、ハハ、独自の記事では、この問題はAQSを行くために、特定の抽象メソッドを記述していない理由を、達成するためのメソッドをオーバーライドする必要がありますtryReleaseSharedそれを見て-私はあなたが答えを見つけることができると信じて
概要
達成するためにコード全体を読んだ後、Semphoreが実際に共有ロックは、複数のスレッドが国家のAQSを共有することができますされ、Semphore一般的な使用シナリオでは、スレッドの数を制限することがあるリソースへの同時アクセス