この記事では、AQSと並行性ツールの関係について学習します2

1.フェアロックを作成します

1.使用方法

Lock reentrantLock = new ReentrantLock(true);
reentrantLock.lock(); //加锁
try{
    
    
  // todo
} finally{
    
    
  reentrantLock.unlock(); // 释放锁
}

2.フェアロックを作成します

新しいReentrantLock(true)のときにキーワードtrueを追加します

public ReentrantLock(boolean fair) {
    
    
    sync = fair ? new FairSync() : new NonfairSync();
}

渡されたパラメータ値がtrueの場合、作成されるオブジェクトは** new FairSync()**フェアロックです。

2.ロックの実現

1.通常の取得ロック

reentrantLock.lock(); //加锁

実際のロック方法は、作成されたフェアロックのロック方法です。
画像

static final class FairSync extends Sync {
    
    
    final void lock() {
    
    
        acquire(1);
    }
    ...
}

コード内のacquireメソッドとunfairlock内のacquireメソッドは、呼び出されるAQSの最後のメソッドです。

## AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

ただし、違いは、tryAcquire(arg)メソッドがと呼ばれるフェアロックに実装されたメソッドであるということです。
画像

このメソッドは、実際にはアンフェアロックメソッドと非常によく似ています。別のフェアロックにhasQueuedPredecessors()という特別なメソッドがあります。このメソッドは、AQSのメソッドでもあります。このメソッドの本質は、の先行ノードがノードはヘッドです。ノード

## AbstractQueuedSynchronizer
public final boolean hasQueuedPredecessors() {
    
    
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

前の記事で分析された残りの部分と不公平なロックはほとんどプロセスです

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 设置当前线程为当前锁的独占线程
            setExclusiveOwnerThread(current);
            // 获取锁成功
            return true;
        }
    }
    // 如果是当前线程持有的锁信息,在原来的state的值上加上acquires的值
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置state的值
        setState(nextc);
        // 获取锁成功
        return true;
    }
    // 获取锁失败了才返回false
    return false;
}

falseを返した場合にのみ、tryAcquireが失敗することに注意してください。この時点で、面倒な** addWaiter(Node.EXCLUSIVE)**メソッドが表示されます。

2.通常のロック取得に失敗しました

前のtryAcquireが失敗した場合、次のaddWaiter(Node.EXCLUSIVE)が実行されます

## AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
    
    
    // 创建一个新的node节点 mode 为Node.EXCLUSIVE = null
    Node node = new Node(Thread.currentThread(), mode);
    // 获取尾部节点
    Node pred = tail;
    // 如果尾部节点不为空的话将新加入的节点设置成尾节点并返回当前node节点
    if (pred != null) {
    
    
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
    
            pred.next = node;
            return node;
        }
    }
    // 如果尾部节点为空整明当前队列是空值得需要将当前节点入队的时候先初始化队列
    enq(node);
    return node;
}

3.ノード参加方法

enq(node)メソッドはノードエンキューのメソッドです。分析してみましょう。enqエンキューメソッドはAQSのメソッドでもあります。このメソッドの無限ループに注意してください。いずれの場合も、ノードをキューに追加する必要があります。 。

## AbstractQueuedSynchronizer
private Node enq(final Node node) {
    
    
for (;;) {
    
    
Node t = tail;
// 如果尾节点为空的话,那么需要插入一个新的节点当头节点
        if (t == null) {
    
    
if (compareAndSetHead(new Node()))
tail = head;
} else {
    
    
    // 如果不为空的话,将当前节点变为尾节点并返回当前节点的前驱节点
        node.prev = t;
if (compareAndSetTail(t, node)) {
    
    
t.next = node;
return t;
}
}
}
}

実際、分析後の不公平なロックのaddWaiter(ノードノード)と同じプロセスです。

4.acquireQueuedメソッド

この時点で、現在のノードがブロッキングキューに追加され、acquireQueuedメソッドに入りました。この方法は、AQSの方法でもあります。

## AbstractQueuedSynchronizer
final boolean acquireQueued(final Node node, int arg) {
    
    
    boolean failed = true;
    try {
    
    
        boolean interrupted = false;
        for (;;) {
    
    
         // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            // 如果当前节点的前驱节点是头节点的话会再一次执行tryAcquire方法获
            // 取锁
            if (p == head && tryAcquire(arg)) {
    
    
             // 设置当前节点为头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
    
    
        if (failed)
            cancelAcquire(node);
    }
}

setHead(node)の特定の実装詳細スレッドがnullであり、prevもnullであることに注意してください。実際、現在のノードの先行ノードがヘッドノードである場合、現在のノードは仮想ヘッドであるヘッドノードになります。以前にブロックされたキューのノード。

private void setHead(Node node) {
    
    
    head = node;
    node.thread = null;
    node.prev = null;
}

それがヘッドノードでない場合、またはtryAcquire()メソッドが次のより厄介なメソッドshouldParkAfterFailedAcquire(p、node)の実行に失敗した場合、メソッドがtrueを返すと、次のparkAndCheckInterrupt()メソッドに対して実行されます。 AQSメソッド。

## AbstractQueuedSynchronizer
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
    // 获取前驱节点的状态
int ws = pred.waitStatus;
// 如果前驱节点的状态为SIGNAL那么直接就可以沉睡了,因为如果一个节点要是进入
    // 阻塞队列的话,那么他的前驱节点的waitStatus必须是SIGNAL状态。
    if (ws == Node.SIGNAL)
return true;
// 如果前驱节点不是Node.SIGNAL状态就往前遍历一值寻找节点的waitStatus必须
    // 是SIGNAL状态的节点
    if (ws > 0) {
    
    
do {
    
    
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
    
    
    // 如果没有找到符合条件的节点,那么就将当前节点的前驱节点的waitStatus
        // 设置成SIGNAL状态
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

戻り値がfalseの場合、この時点で次の無限ループに入るのに注意する必要があります。これは、前に移動する過程で前ノードがヘッドノードになる可能性がある場合、ロックを再度取得できるためです。そうでない場合は
、parkAndCheckInterrupt()メソッドを実行してスレッドを一時停止することしかできません

private final boolean parkAndCheckInterrupt() {
    
    
LockSupport.park(this);
return Thread.interrupted();
}

5.リクエストをキャンセルします

いずれにせよ、私はついにcancelAcquireメソッドにたどり着きました

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;
// 将当前节点设置成取消状态,为了后续遍历跳过我们
    node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,并且将当前节点的前驱节点设置成尾节点成功
    if (node == tail && compareAndSetTail(node, pred)) {
    
    
    // 当前节点的前驱节点的后续节点为空
    compareAndSetNext(pred, predNext, null);
} else {
    
    
    int ws;
// 如果前驱节点不是头节点
        if (pred != head &&
        // 前驱节点的状态是Node.SIGNAL或者前驱节点的waitStatus设置           
        // 成Node.SIGNAL
        ((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
// 前驱节点的thread 不为空
            pred.thread != null) {
    
    
// 获取当前节点的后继节点
            Node next = node.next;
// 如果后继节点不为空,并且后继节点waitStatus 小于0
            if (next != null && next.waitStatus <= 0)
// 将当前节点的后继节点设置成当前节点的前驱节点的后继节点
            compareAndSetNext(pred, predNext, next);
} else {
    
    
// 如果上面当前节点的前驱节点是head或者其他条件不满足那么就唤醒当前节点
        unparkSuccessor(node);
}
node.next = node; // help GC
}
}

unparkSuccessor(node)は現在のノードをウェイクアップします。このメソッドは、AbstractQueuedSynchronizerのメソッドでもあります。

private void unparkSuccessor(Node node) {
    
    
    // 获取当前节点的状态
    int ws = node.waitStatus;
    // 如果当前节点状态小于0那么设置成0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取当前节点的后继节点
    Node s = node.next;
    // 如果后继节点为空,或者后继节点的状态小于0
    if (s == null || s.waitStatus > 0) {
    
    
        // 后继节点置为null。视为取消请求的节点
        s = null;
        // 获取尾节点,并且尾节点不为空,不是当前节点,那么就往前遍历寻找
        // 节点waitStatus 状态小于0的节点赋予给当前节点的后继节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
     // 唤醒后继节点
        LockSupport.unpark(s.thread);
}

3.ロック取得のフローチャート

フロー・チャートである以前に非常に似て、不公平なロックを取得するためのフローチャート少しだけ違い。私はあまりここでは説明しません。

4.リリースロックの実装

4.1リリースロックコードの分析

このロックを解除してみてください。現在のスレッドがこのロックのホルダーである場合、予約数は減少します。ホールドカウントがゼロになると、ロックが解除されます。現在のスレッドがこのロックの所有者でない場合、IllegalMonitorStateExceptionがスローされます。

## ReentrantLock
public void unlock() {
    
    
    sync.release(1);
}

sync.release(1)は、AbstractQueuedSynchronizerのreleaseメソッドを呼び出します

## AbstractQueuedSynchronizer
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(arg)メソッドを分析します
画像

tryRelease(arg)このメソッドはReentrantLockで呼び出されます

protected final boolean tryRelease(int releases) {
    
    
// 获取当前锁持有的线程数量和需要释放的值进行相减
    int c = getState() - releases; 
    // 如果当前线程不是锁占有的线程抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果此时c = 0就意味着state = 0,当前锁没有被任意线程占有
    // 将当前所的占有线程设置为空
    if (c == 0) {
    
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置state的值为 0
    setState(c);
    return free;
}

ヘッドノードが空でなく、waitStatus!= 0の場合、後続のノードが存在する場合はウェイクアップします。
判断条件h!= null && h.waitStatus!= 0なのはなぜですか?

h == nullであるため、Headはまだ初期化されていません。初期の状況では、head == nullで、最初のノードがキューに入れられ、Headが仮想ノードとして初期化されます。したがって、チームに参加する時間がない場合、head == nullが発生します。

  1. h!= null && waitStatus == 0は、後続ノードに対応するスレッドがまだ実行中であり、ウェイクアップする必要がないことを示します
  2. h!= null && waitStatus <0は、後続ノードがブロックされている可能性があり、ウェイクアップする必要があることを示します
private void unparkSuccessor(Node node) {
    
    
// 获取头结点waitStatus
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
// 获取当前节点的下一个节点
    Node s = node.next;
//如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
    if (s == null || s.waitStatus > 0) {
    
    
        s = null;
        // 就从尾部节点开始找往前遍历,找到队列中第一个waitStatus<0的节点。
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
  // 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}

キャンセルされていない最初のノードを後ろから前に見つけたいのはなぜですか?
addWaiterメソッドを見てください

private Node addWaiter(Node mode) {
    
    
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
    
    
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
    
    
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

このことから、チームへのノードのエントリはアトミック操作ではないことがわかります。つまり、node.prev = pred、compareAndSetTail(pred、node)これらの2つの場所は、テール登録のアトミック操作と見なすことができます。 、ただし、現時点ではpred。next= node;はまだ実行されていません。この時点でunparkSuccessorメソッドが実行された場合、前から後ろに検索する方法がないため、後ろからに検索する必要があります。前面。別の理由があります。CANCELED状態ノードが生成されると、Nextポインターが最初に切断され、Prevポインターが切断されないため、すべてのノードをトラバースするには、後ろから前にトラバースする必要があります
したがって、極端な場合のエンキューの非アトミック操作と、CANCELEDノードの生成中にNextポインターを切断する操作のために、検索が前から後ろに行われる場合、すべてのノードをトラバースできない場合がありますしたがって、対応するスレッドをウェイクアップした後、対応するスレッドは実行を継続します。

4.2ロック解除フローチャート

画像

5.注意

次の記事では、並行性ツールキットの下でのLockSupportについて説明します。ご清聴ありがとうございました!問題がありますので、みんなで指摘して一緒に進んでください!!!

おすすめ

転載: blog.csdn.net/weixin_38071259/article/details/112789424