ホームページ  >  記事  >  Java  >  ReentrantLockの実装原理の紹介(コード例)

ReentrantLockの実装原理の紹介(コード例)

不言
不言転載
2019-01-31 11:14:033005ブラウズ

この記事では、ReentrantLock の実装原理 (コード例) を紹介します。必要な方は参考にしてください。

並行プログラミングでは、synchronized キーワードに加えて、Java 並行性パッケージの java.util.concurrent.locks の ReentrantLock および ReentrantReadWriteLock も一般的に使用されるロック実装です。この記事では、リエントラント ロックの原理をソース コードから分析します。

まず、再入可能ロックについて説明します。スレッドはロックを取得した後、自身をブロックせずに複数回ロックを取得できます。

ReentrantLock は、抽象クラス AbstractQueuedSynchronizer (以下、AQS) に基づいて実装されます。

ソース コードを見てください:

まず、コンストラクターから、ReentrantLock には公平なロックと不公平なロックという 2 つのメカニズムがあることがわかります。

//默认非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
    }

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

まず、公平なロックと不公平なロックの違いを簡単に説明し、次に 2 つの異なる実装方法を分析します。

公平なロック: 複数のスレッドは先着順です。キューイングと同様に、後から来るスレッドはキューの最後に配置されます。

不公平なロック: ロックを奪い合います。掴まれていれば実行され、掴まれていなければブロックされます。ロックを取得したスレッドが解放されるまで待ってから、競争に参加してください。

したがって、通常は不公平なロックが使用されます。フェアロックよりも効率が高いです。

Get lock

Fair lock

final void lock() {
            acquire(1);
        }

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

最初のステップは tryAcquire(arg) try ですFairSync によって実装された Lock を追加するための具体的なコードは次のとおりです:

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;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  • ##現在のスレッドを取得

  • # AQS で状態を取得します。 state が 0 の場合は、現時点でロックを取得しているスレッドがないことを意味します。
  • if 判定では、まず AQS ノードのキューが空かどうかを判定します。空いていない場合は並ぶ必要があります。現時点ではロックは取得されていません。
  • CAS アルゴリズムを使用して状態を 1 に更新してみます。更新が成功し、ロックが取得され、このときのスレッドが排他スレッド exclusiveOwnerThread に設定されます。 trueを返します。
  • state が 0 でない場合は、スレッドがすでにロックを取得していることを意味します。したがって、ロックを取得したスレッド(排他スレッド)がカレントスレッドであるかどうかを判断する必要があります。
  • 「はい」の場合、それは再入可能であることを意味します。状態を 1 増やします。 trueを返します。
  • 最後のステップでは、ロックは取得されません。 false を返す;
  • ロックの取得に失敗した場合は、まず addWaiter(Node.EXCLUSIVE) を実行し、現在のスレッドをキューに書き込みます。
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

新しいノードノードをカプセル化します

  • リンクされたリストの末尾が空かどうかを判断します。空でない場合は、新しいノード node' を最後まで書き込みます

  • 'リンクされたリストの最後が空の場合は、enq(node) を使用して最後まで書き込みます。

  • キューに書き込んだ後、acquireQueued() メソッドは現在のスレッドを一時停止します。

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; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

ループ内で、前のノードがヘッド ノードの場合は、再度ロックの取得を試みます。成功した場合、ループは終了し、 false が返されます。

  • は、前のノードの waitStatus に基づいて、現在のスレッドを実行する必要があるかどうかを判断します。一時停止中。 waitStatus は、ノードのキャンセル、ノードの待機などのノードのステータスを記録するために使用されます。

  • 一時停止する必要があると判断した場合は、parkAndCheckInterrupt() メソッドを使用してスレッドを一時停止します。具体的には、LockSupport.park(this) を使用してスレッドを一時停止します。

  • 最初のステップでロックの取得が成功した場合は、このノードのロック取得操作をキャンセルできます。

  • #不公平なロック

不公平なロックには、ロックの取得戦略に違いがあります。

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

不正なロックは、まず CAS アルゴリズムを直接使用して状態を更新し、ロックを取得しようとします

  • 更新に失敗した後、ロックを取得しようとしたとき

  • ##
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }

    #公平なロックと比較すると、不公平なロックが発生します。 lock はロックの取得を試みます。キュー内に他のスレッドがあるかどうかを判断する必要はありません。

ロックを解放します

ロックを解放する手順は、公平なロックと不公平なロックの両方で同じです

public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
//更新state
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
これは再入可能なロックであるため、ロックが完全に解放されたとみなされる前に、tryRelease() メソッドで状態を 0 に更新する必要があることに注意してください。解放後、中断されたスレッドを起動します。

以上がReentrantLockの実装原理の紹介(コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。