Javaのマルチスレッド(9)
AQS(2)
所持してロックを解除
AQSのために、重要なスレッド同期状態は、動作状態の値であるかどうか、スレッドの状態に応じて共有され、排他的に動作状態モードに分割されます
- 使用されたリソースとメソッドの放出を得るために排他的な方法の下では、次のとおりです。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- メソッドの取得と解放リソースへの道の下でパン共有:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
スレッドがリソースを取得している、それはこのスレッドをマークする特定の結合スレッドに独占的に利用可能なリソースを使用して、他のスレッドが試す得ることですリソースの現在の運転状態が、彼らが保持するリソースを取得していないでしょう、失敗した買収後にブロックされます
複数のスレッドが、リソースの競合のCASモードによって、リソースへのアクセスを要求するスレッドがリソースに到達したときに、現在のリソースがそのニーズを満たすことができれば、別のスレッドが再び取得する際に、特定のアプローチでスレッド共有リソースは関係ありません、現在のスレッドは取得のみにCASの方法を使用する必要があります
独占取得と解放リソースのプロセス:
- (1)
当一个线程调用 acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体就是设置状态变量state的值,成功就直接返回,失败就将当前线程封装为类型Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- (2)
当一个线程调用release方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread),被激活的线程使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下进行,否则还是会被放入AQS队列并被挂起
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
詳細:
成功したが、それ以外の場合はfalseを返した場合AQSベースおよび状態ステータス値を変更するシーンCASアルゴリズムの試みのためにこれらのメソッドを実装する際に使用され、これは特定のサブクラスを必要と達成することが可能なtryAcquire tryRelease方法、サブクラスを提供していない、trueを返します、取得および解放メソッドの増減状態ステータス値の意味は何を呼び出すときに、サブクラスを定義する必要があります
取得および解放のプロセスにリソースを共有します:
- (1)
线程调用acquireShared获取共享资源时,首先使用tryAcquireShared尝试获取资源,具体设置状态变量state的值,成功则直接返回,失败则将当前线程封装为Node.SHARED的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- (2)
当一个线程调用releaseShared时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread),被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
詳細:
AQSクラスが有用とtryReleaseShared tryAcquireShared方法を提供していない、2つの方法は2つの方法がこれを達成する際のシーンのために、ステータス値CASアルゴリズムの使用状態を変更しようと、サブサブクラスを実装する必要があり、そうでない場合は、成功した場合trueを返します偽
AQSは、上記の方法に加えて実施基づくロックが現在のスレッドロックは排他的または共有されているかどうかを決定するためにisHeldExclusively方法を書き換えることも書き換える必要があるが、
割り込み可能のキーワードなしでどのメソッドの意味は、故障が中断されたときに、割り込み可能キーワードやリソースへのアクセスのないスレッドメソッド呼び出し内のリソースへのアクセスである割り込み応答、スレッドを中断し、他のスレッド、スレッドではありません中断して例外をスローされることはありません、それが割り込みを無視して、割り込み応答しないことを、リソースへのアクセスを継続するか、中断され
排他ロックモード
ロック取得処理:
-
スレッドがロックリソースを取得するための取得()アプリケーションを呼び出すと、成功すれば、重要な領域に入ります。
-
ロックの取得に失敗すると、その後、FIFOキューを入力し、ウェイクを待つために停止しました。
-
スレッドキューを再び起こされるのを待っているときに成功した場合には、クリティカルセクションを入力するか、ハングアップしたように待ち続け、リソースのロックを取得しようとし
ロック解除方法:
-
他のスレッドがリソースのロックを待機されていない場合、スレッドが解放()ロック・リソースを解放する呼び出すと、その後、完全なリリース
-
他のスレッドがロックリソースのためにそこに待っているキューがウェイクアップする必要がある場合は、キュー内の最初のノードは、(先入れ先出し)でウェイクアップを待っています
取得(int型引数)
ロックを取得します:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ロジック:
-
ロックリソースを取得するtryAcquire()メソッドの試みのコールサブクラス実装では、成功した場合、全体の取得は、()メソッドが終了すると、現在のスレッドがロックリソースを取得することを、あなたはクリティカルセクションを入力することができます
-
ロック獲得に失敗した場合は、ロジックは、最初にaddWaiter(Node.EXCLUSIVE)メソッドは、背後に入り始めます
// 该入队方法的返回值就是新创建的节点
private Node addWaiter(Node mode) {
//基于当前线程,节点类型(Node.EXCLUSIVE)创建新的节点
//由于这里是独占模式,因此节点类型就是Node.EXCLUSIVE
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//这里为了提搞性能,首先执行一次快速入队操作,即直接尝试将新节点加入队尾
if (pred != null) {
node.prev = pred;
//这里根据CAS的逻辑,即使并发操作也只能有一个线程成功并返回,其余的都要执行后面的入队操作。即enq()方法
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
エンキュー操作:
//完整的入队操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果队列还没有初始化,则进行初始化,即创建一个空的头节点
if (t == null) {
//同样是CAS,只有一个线程可以初始化头结点成功,其余的都要重复执行循环体
if (compareAndSetHead(new Node()))
tail = head;
} else {
//新创建的节点指向队列尾节点,毫无疑问并发情况下这里会有多个新创建的节点指向队列尾节点
node.prev = t;
//基于这一步的CAS,不管前一步有多少新节点都指向了尾节点,这一步只有一个能真正入队成功,其他的都必须重新执行循环体
if (compareAndSetTail(t, node)) {
t.next = node;
//该循环体唯一退出的操作,就是入队成功(否则就要无限重试)
return t;
}
}
}
}
-
トリガは、キューの初期化現在のスレッドがロックがリソースを持っている所有している(それは、任意のプロパティを設定していないが)、現在のノード上に作成された空のヘッドノードは、ロックリソースの所有であると考えられます
-
全体のコードは、チームが成功することを知って、死のサイクルです。それが失敗した場合は、再試行していきます
上記の操作の後、スレッドが正常にキューに参加しているアプリケーションのロックを取得し、ここで実行し、次のノードが起こされるのを待って、現在のスレッドを中断することです
// 该方法入参node就是刚入队的包含当前线程信息的节点
final boolean acquireQueued(final Node node, int arg) {
//锁资源获取失败标记位
boolean failed = true;
try {
//等待线程被中断标记位
boolean interrupted = false;
//这个循环体执行的时机包括新节点入队和队列中等待节点被唤醒两个地方
for (;;) {
//获取当前节点的前置节点
final Node p = node.predecessor();
//如果前置节点就是头结点,则尝试获取锁资源
if (p == head && tryAcquire(arg)) {
//当前节点获得锁资源以后设置为头节点
//头结点就表示当前正占有锁资源的节点
setHead(node);
p.next = null; //帮助GC
//表示锁资源成功获取,因此把failed置为false
failed = false;
//返回中断标记,表示当前节点是被正常唤醒还是被中断唤醒
return interrupted;
}
如果没有获取锁成功,则进入挂起逻辑
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取锁失败处理逻辑
if (failed)
cancelAcquire(node);
}
}
これまでのロジックを中断しますが、現在のスレッドによると、ノードの種類は、ノードを作成し、キューに参加し、他の属性はデフォルト値です
//node是当前线程的节点,pred是它的前置节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的waitStatus
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前置节点的waitStatus是Node.SIGNAL则返回true,然后会执行parkAndCheckInterrupt()方法进行挂起
return true;
if (ws > 0) {
//由waitStatus的几个取值可以判断这里表示前置节点被取消
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//这里我们由当前节点的前置节点开始,一直向前找最近的一个没有被取消的节点
//注,由于头结点head是通过new Node()创建,它的waitStatus为0,因此这里不会出现空指针问题,也就是说最多就是找到头节点上面的循环就退出了
pred.next = node;
} else {
//根据waitStatus的取值限定,这里waitStatus的值只能是0或者PROPAGATE,那么我们把前置节点的waitStatus设为Node.SIGNAL然后重新进入该方法进行判断
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
ロジックの上記の方法は、現在のノードが、すなわち、ハングしている場合、他のスレッドを起動することが可能でなければならない、ウェイクアップ条件が満たされたか否か、すなわち、懸濁され得るかどうかを決定します。このメソッドがfalseを返した場合、保留されている状態が完全ではないこと、それは)(ループ本体は、メソッド、再評価をacquireQueuedとtrueを返した場合、それはあなたがparkAndCheckInterruptに入り、ハングアップすることができ、すべての準備が整ったことを再実行します方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//被唤醒之后,返回中断标记,即如果是正常唤醒则返回false,如果是由于中断醒来,就返回true
return Thread.interrupted();
}
最終ステップacquireQueued法、最後にモジュール。ここではいくつかの改善作業を行うには、後の検索のロックリソースの障害があります
//传入的方法参数是当前获取锁资源失败的节点
private void cancelAcquire(Node node) {
// 如果节点不存在则直接忽略
if (node == null)
return;
node.thread = null;
// 跳过所有已经取消的前置节点,跟上面的那段跳转逻辑类似
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//这个是前置节点的后继节点,由于上面可能的跳节点的操作,所以这里可不一定就是当前节点,仔细想一下。^_^
Node predNext = pred.next;
//把当前节点waitStatus置为取消,这样别的节点在处理时就会跳过该节点
node.waitStatus = Node.CANCELLED;
//如果当前是尾节点,则直接删除,即出队
//注:这里不用关心CAS失败,因为即使并发导致失败,该节点也已经被成功删除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//这里的判断逻辑很绕,具体就是如果当前节点的前置节点不是头节点且它后面的节点等待它唤醒(waitStatus小于0),
//再加上如果当前节点的后继节点没有被取消就把前置节点跟后置节点进行连接,相当于删除了当前节点
compareAndSetNext(pred, predNext, next);
} else {
//进入这里,要么当前节点的前置节点是头结点,要么前置节点的waitStatus是PROPAGATE,直接唤醒当前节点的后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
ロックを解除
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease()メソッドは、ロック・ロジックが成功した場合、起こされるのを待っているキューにノードが存在しないと判定され、サブクラスで実装(waitStatus 0アウェイクすべきノードの必要性を示していない)されているリリース
ウェイクアップ動作
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//把标记为设置为0,表示唤醒操作已经开始进行,提高并发环境下性能
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//如果当前节点的后继节点为null,或者已经被取消
if (s == null || s.waitStatus > 0) {
s = null;
//注意这个循环没有break,也就是说它是从后往前找,一直找到离当前节点最近的一个等待唤醒的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//执行唤醒操作
if (s != null)
LockSupport.unpark(s.thread);
}
共有ロックモード
ロック取得処理:
-
スレッドがロックリソースを取得するacquireShared()アプリケーションを呼び出すと、成功した場合、クリティカル領域を入力します。
-
ロックが失敗した取得する場合、中断され、その後、待つウェイクノードのとFIFOキューに共有タイプを作成
-
スレッドキューを再び起こされるのを待っているときに共有ノードの背後に成功したきっかけとはまだ一緒に渡すためにウェイクアップイベントを待っている場合は、ノードの背面にあるすべての共有ノード目覚め、リソースのロックを取得するために順番にその意志を試み、その後、入力しますクリティカルセクションは、そうでない場合は待機をハングアップし続けています。
ロック解除方法:
- スレッドがリリースされるreleaseShared()のロックリソースを呼び出すとリリースが成功した場合、もしあれば、キュー内のノードの待ち時間は、目覚めています。
ロックを取得します:
public final void acquireShared(int arg) {
//尝试获取共享锁,返回值小于0表示获取失败
if (tryAcquireShared(arg) < 0)
//执行获取锁失败以后的方法
doAcquireShared(arg);
}
tryAcquireShared()メソッドを実装するために左ロックロジック特定のサブクラスを取得することです
このアプローチの実現には、特別な説明が必要な2つの点があります
-
この方法はさらに、買収によってサポートされている場合、現在のコンテキストの取得に共有ロックをサポートするかどうかを自分自身を確認する必要があります。
-
このメソッドは、値がキーで返します。まず、上記のコードフラグメントは、戻り値がより少ない0がキューを入力する必要があり、ロックの獲得に失敗したことを示している以上であることがわかります。戻り値が0に等しい場合は第二に、現在のスレッドが共有ロックの成功を獲得することを示しているが、それは、その背後に待機しているウェイクノードにする必要はありませんされて得られた糸を、従うことを継続することはできません。最後に、戻り値が0より大きい場合は、可能性の高い共有ロックを獲得し続けることもあり、現在のスレッドがノードを待っている共有ロックとその後の成功を獲得することを示している共有ロックを取得するノードの試みを覚ますためにそれらを追跡するために、この時間が必要で言うことですつまり、成功しています。
doAcquireShared()
//参数不多说,就是传给acquireShared()的参数
private void doAcquireShared(int arg) {
//添加等待节点的方法跟独占锁一样,唯一区别就是节点类型变为了共享型,不再赘述
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//表示前面的节点已经获取到锁,自己会尝试获取锁
if (p == head) {
int r = tryAcquireShared(arg);
//注意上面说的, 等于0表示不用唤醒后继节点,大于0需要
if (r >= 0) {
//这里是重点,获取到锁以后的唤醒操作,后面详细说
setHeadAndPropagate(node, r);
p.next = null;
//如果是因为中断醒来则设置中断标记位
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//挂起逻辑跟独占锁一样,不再赘述
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取失败的取消逻辑跟独占锁一样,不再赘述
if (failed)
cancelAcquire(node);
}
}
最初のノードを設定し、メソッドのsetHeadAndPropagateを呼び出して、首尾よく成功のために排他ロックモード、プロセスの終了、以降共有ロックモードを取得した後、割り込みステータスを返します。
//两个入参,一个是当前成功获取共享锁的节点,一个就是tryAcquireShared方法的返回值,注意上面说的,它可能大于0也可能等于0
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; //记录当前头节点
//设置新的头节点,即把当前获取到锁的节点设置为头节点
//注:这里是获取到锁之后的操作,不需要并发控制
setHead(node);
//这里意思有两种情况是需要执行唤醒操作
//1.propagate > 0 表示调用方指明了后继节点需要被唤醒
//2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//如果当前节点的后继节点是共享类型获取没有后继节点,则进行唤醒
//这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
if (s == null || s.isShared())
//后面详细说
doReleaseShared();
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
ウェイクアップ操作:
private void doReleaseShared() {
for (;;) {
//唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
//其实就是唤醒上面新获取到共享锁的节点的后继节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//表示后继节点需要被唤醒
if (ws == Node.SIGNAL) {
//这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//执行唤醒操作
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//如果头结点没有发生变化,表示设置完成,退出循环
//如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
if (h == head)
break;
}
}
共有ロックが解除されます
public final boolean releaseShared(int arg) {
//尝试释放共享锁
if (tryReleaseShared(arg)) {
//唤醒过程,详情见上面分析
doReleaseShared();
return true;
}
return false;
}
排他ロックを比較すると、主な機能は、言っている成功したロック(それは共有ロックを取得します)を取得キューで待機した後、共有ノードのロックを共有したときに、それが共有されているので、それはその背後にあるすべての航跡が続かなければならないということですカレントノードとは、リソースロックを共有しました。
確かに、これらのノードは、(これは排他ロック待ち場合、それはすでにロックを獲得するために共有ノードを持っているという前提で、それは買収よりも確かに少ないです)共有ロックを待機しなければなりません。共有ロックが解放されると、それは読み取りロックが解除されると、読み書きロックで考える例することができ、その後、いずれかの読み取りロックまたは書き込みロックは、すべてのリソースの競合することができます