JUC-El principio de AQS a partir de la realización de ReentrantLock

¡Acostúmbrate a escribir juntos! Este es el día 13 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

1. ¿Qué es AQS?

La mayoría de las clases de sincronización en Java (Lock, Semaphore, ReentrantLock, etc.) se implementan en base a AbstractQueuedSynchronizer (AQS para abreviar). AQS es un marco simple que proporciona administración atómica del estado de sincronización, la capacidad de bloquear y reactivar subprocesos y un modelo de cola.

2. La estructura de la clase AQS

AQS clase estructura imagen.pngcabeza cabeza puntero cola cola puntero estado estado

La estructura del nodo es la siguiente

imagen.png

Explicación relacionada

imagen.png

3. El principio básico de AQS

La idea central de AQS es que si el recurso compartido solicitado está inactivo, entonces el subproceso que actualmente solicita el recurso se establece en un subproceso de trabajo válido y el recurso compartido se establece en un estado bloqueado; si el recurso compartido está ocupado, se requiere un cierto mecanismo de activación de bloqueo y espera para garantizar la asignación de bloqueo. Este mecanismo se implementa principalmente mediante una variante de la cola CLH, que agrega subprocesos que no pueden adquirir temporalmente bloqueos en la cola.

CLH: Las colas de Craig, Landin y Hagersten son listas enlazadas individualmente. Las colas en AQS son colas bidireccionales virtuales (FIFO) de variantes de CLH. AQS implementa la asignación de bloqueos encapsulando cada subproceso que solicita recursos compartidos en un nodo. .

El diagrama esquemático principal es el siguiente:

imagen.png

4. Ver el principio de AQS de la implementación de ReentrantLock

Supongamos que hay tres subprocesos para adquirir el bloqueo.

El subproceso que agarra el bloqueo cambiará el estado de 0 a 1. En este momento, otros subprocesos ejecutarán la lógica de preferencia pendiente si no se toman.

Es decir, el método de adquisición () en la siguiente figura

imagen.pngAbrimos para ver el método de adquisición.

Tres métodos se ejecutan secuencialmente.

intentar adquirir (arg)

adquirirQueued(addWaiter(Nodo.EXCLUSIVO), arg)

imagen.png

El primer método tryAcquire intenta adelantarse:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //状态为0直接抢占到
    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;
}
复制代码

Como se mencionó anteriormente, el primer subproceso ha cambiado el estado de 0 a 1 y durante la ejecución, el segundo subproceso no puede agarrarlo y vuelve a falso

Luego se ejecutará el método addWaiter()

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;
}
复制代码
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
复制代码

由于第二个线程是第一个进来抢占的 所以tail为空 需要先构建双向队列 执行enq方法 该方法先创建了一个哨兵节点作为head 然后将第二个线程封装的节点放在了其后面 尾指针指向第二个线程的节点 如下图所示

imagen.png

接下来 会进入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;
            }
            //由于第一个线程还未释放锁 state状态还是1 所以抢占失败 进入下面逻辑
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

第二个线程节点上一个节点为头节点 尝试抢占,由于第一个线程还未释放锁 state状态还是1 所以抢占失败 进入下面逻辑shouldParkAfterFailedAcquire(p, node)&& parkAndCheckInterrupt()

先看第一个方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
复制代码

进入的时候节点的前一个节点waitstatus为0 将waitstatus状态改为-1表示进入阻塞 返回false 接着上一步的自旋,此时又抢占失败 进入了这个方法,由于waitstatus状态已经为-1 所以返回true 进入第二个方法 parkAndCheckInterrupt()

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

这个方法使用LockSupport.park(this) 精准park了这个线程 此时 线程二阻塞在这一行代码。

就在此时线程一跑完了 执行了unlock方法 我们来看下unlock方法

public void unlock() {
    sync.release(1);
}
复制代码

使用下面方法释放锁 将setExclusiveOwnerThread设置为null state设置为0

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;
}
复制代码

将state改为0之后 拿到队列的头节点 由于头节点在上面已经不为空 并且waitstatus被改为了-1,这个时候执行unparkSuccessor()

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
复制代码

这个时候执行unparkSuccessor

private void unparkSuccessor(Node node) {
    
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
   
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
复制代码

现将节点状态改为0 然后使用LockSupport精准唤醒节点的下一个结点 也就是第二个线程的节点,此时第二个线程被唤醒

imagen.png

此时state状态为0 进入第一个if逻辑抢锁成功,将自己设置为头节点 并清除和上一个头节点的引用,帮助垃圾回收

如此 循环往复 就是AQS实现ReentrantLock的基本原理了

Este artículo se centra en el método de desbloqueo de bloqueo ReentrantLock, que también incluye lógica como la cancelación de interrupciones. Este artículo no lo describirá. Hablaré contigo cuando tenga la oportunidad.

Supongo que te gusta

Origin juejin.im/post/7085982722270691365
Recomendado
Clasificación