Comprender profundamente la variable de condición Condition

concepto

La condición es la nueva interfaz estándar java.util.concurrent.locks.Condition de la biblioteca introducida por JDK1.5.
La interfaz Condition se puede utilizar como sustituto de la espera / notificación para implementar la espera / notificación.Proporciona soporte para resolver el problema de la activación prematura y resuelve el problema de que Object.wait (largo) no puede distinguir si la devolución se debe a un tiempo de espera .
La instancia de condición se puede obtener a través de Lock.newCondition (), lo que significa que cualquier método newCondition que muestre una instancia de bloqueo puede obtener una instancia de condición.
Object.wait / notify requiere que su hilo de ejecución mantenga el bloqueo interno del objeto al que pertenecen estos métodos, como sincronizar

utilizar

El uso de condiciones es similar a esperar / notificar. como sigue:

Consumidor .java

public class Consumer implements Runnable {
    
    
    private Queue<String> msg;
    private int maxSize;
    private Lock lock;
    private Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
    
    
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    
            lock.lock();
            while (msg.isEmpty()) {
    
    
                //如果消息队列为空了
                try {
    
    
                    System.out.println("第一次阻塞");
                    condition.await(); //阻塞当前线程
                    System.out.println("第二次唤醒");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("消费者消费消息:" + msg.remove());
            condition.signal(); //唤醒处于阻塞状态下的生产者
            lock.unlock();
        }
    }
}

Producto .java

public class Product implements Runnable{
    
    
    private Queue<String> msg;
    private int maxSize;
    private Lock lock;
    private Condition condition;

    public Product(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
    
    
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
    
    
        int i=0;
        while(true){
    
    
            i++;
            lock.lock();
            while(msg.size()==maxSize){
    
    
                //如果生产满了
                try {
    
    
                   condition.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("生产者生产消息:"+i);
            msg.add("生产消息:"+i);
            condition.signal();
            lock.unlock();
        }
    }
}

Análisis de código fuente

Nota: Comentaré la función de cada método en el código fuente, puede consultar los comentarios para comprenderlo. La explicación después del código será la menor posible.

A continuación, nos centraremos en dos métodos, que también son los dos métodos más utilizados: await () y signal ()

esperar()

public final void await() throws InterruptedException {
    
    
	//线程中断,抛出异常
   if (Thread.interrupted())
        throw new InterruptedException();
    //创建node节点,并将此节点加到Condition下的队列的最后一个
    Node node = addConditionWaiter();
    //让持有该锁的所有线程释放锁(包括重入),同时去唤醒AQS队列中的一个线程
    long savedState = fullyRelease(node);
    int interruptMode = 0;
    //判断此节点是否在AQS队列中
    while (!isOnSyncQueue(node)) {
    
    
     //第一次进来,肯定会返回false,接着将自己阻塞,等待被唤醒
        LockSupport.park(this);
        //signal唤醒之后,继续往下执行
        //判断在等待过程中线程是否被中断,如果线程没中断,则会继续执行循环
        //再次去isOnSyncQueue判断,改节点是否在AQS中
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        	//表示线程被中断,跳出循环
            break;
    }
    // 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
    // 将这个变量设置成 REINTERRUPT.表示需要重新中断,不会抛出异常
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
     //清理掉当前节点之后状态为cancelled的节点
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 如果线程被中断了,需要抛出异常.或者什么都不做
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

Entre, primero juzgue si el hilo actual tiene una marca de interrupción, si es así, lance una excepción de interrupción directamente y entréguela a la capa superior para su procesamiento; de lo contrario, debe ejecutarseaddConditionWaiterMétodo para crear un nodo y cargarlo en la cola de condiciones

addConditionWaiter ()

private Node addConditionWaiter() {
    
    
   //获取最后一个节点
   Node t = lastWaiter;
    // 如果最后一个节点的状态是cancelled,直接out
    if (t != null && t.waitStatus != Node.CONDITION) {
    
    
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //创建当前线程的节点,状态为CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

Si se cancela el estado del último nodo , se limpiará directamente. A continuación, cree el nodo del hilo actual e inicialice el estado del nodo actual en CONDICIÓN.

  • Si el último nodo está vacío, esto asignará el nodo actual a firstWaiter y al mismo tiempo asignará el nodo actual a lastWaiter
  • Si no está vacío, después de agregar el nodo actual al nodo lastWaiter, asigne el nodo actual a lastWaiter

Inmediatamente después, suelte el bloqueo

FullyRelease (nodo)

final long fullyRelease(Node node) {
    
    
    boolean failed = true;
    try {
    
    
    	//获得当前锁的状态值state
        long savedState = getState();
        //去释放锁,包括重入锁,全部清0
        if (release(savedState)) {
    
    
            failed = false;
            return savedState;
        } else {
    
    
            throw new IllegalMonitorStateException();
        }
    } finally {
    
    
    	//如果失败,将当前节点状态置为CANCELLED
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
public final boolean release(long arg) {
    
    
     if (tryRelease(arg)) {
    
    
         Node h = head;
         if (h != null && h.waitStatus != 0)
             unparkSuccessor(h);
         return true;
     }
     return false;
}

Suelte aquí, solo hablando antes de ReentrantLock es lo mismo, no aquí por duplicado, antes de que pueda ir directamente al artículo para comprender en profundidad ReentrantLock

isOnSyncQueue (nodo)

final boolean isOnSyncQueue(Node node) {
    
    
		//判断当前节点的状态
		//第一次进来的时候状态肯定为CONDITION ==》返回false
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
            
        //node.prev可以为非null,但尚未排队,
        //因为将CAS放入队列的CAS可能会失败。 
        //因此,我们必须从尾部开始遍历以确保它确实做到了。 
        //在此方法的调用中,它将始终处于尾部,除非CAS失败(这不太可能),
        //否则它将一直存在,因此我们几乎不会遍历太多。
        return findNodeFromTail(node);
    }

De hecho, el fasle siempre se devuelve la primera vez, entrando así en el bucle y bloqueándose.LockSupport.park (esto), En este punto, Condition liberó con éxito el bloqueo de bloqueo y se bloqueó.
Después de despertar, se ejecutará el siguiente método if

checkInterruptWhileWaiting (nodo)

private int checkInterruptWhileWaiting(Node node) {
    
    
	 //是否有中断标记
   return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

Los significados de THROW_IE y REINTERRUPT son los siguientes:

  • THROW_IE: Se lanza InterruptedException mientras se espera la salida
  • REINTERRUPT: Vuelva a interrumpir mientras espera la salida, es decir, no hay operación.
    Vuelva a verificar el estado de la interrupción. Si se interrumpe, ejecute el método == transferAfterCancelledWait (nodo) == para cambiar el estado del nodo a 0 y agregarlo a la cola AQS al mismo tiempo
final boolean transferAfterCancelledWait(Node node) {
    
    
	//CAS操作将node状态变为0
	//然后添加到AQS的队列中
   if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    
    
   		//添加到AQS的队列中
        enq(node);
        return true;
    }
    //如果我们失败,那么我们将无法继续
    //直到完成其enq()。 
    //因为几乎是不可能会失败的,所以采用自旋的方式
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

Después de salir del ciclo while, continúe ejecutandoadquirirQueued (nodo, SavedState)Método para apropiarse del bloqueo, pero también para determinar que el estado del valor de interruptMode no puede ser igual a THROW_IE, que es el estado de interrupción.
Después de que ambos métodos tengan éxito. El valor de interruptMode para REINTERRUPT , es decir, sin impacto, sin operación.
Maneras de adelantarse al bloqueoadquirirQueued (nodo, SavedState)También en el artículo anterior se menciona una comprensión profunda de ReentrantLock , sin explicación aquí.
Finalmente, se trata de limpiar los nodos cuyo estado es CANCELADO detrás del nodo actual

para resumir

  • En Condition, se mantiene una cola. Cuando se ejecuta el método de espera, se crea un nodo y se agrega a la cola.
  • Luego suelte el bloqueo y active un hilo bloqueado en la cola AQS del bloqueo, que es consistente con la lógica ReentrantLock
  • Bloquearse
  • Después de ser despertado por otros subprocesos, coloque el nodo justo ahora en la cola de AQS

A continuación, veamos cómo se despierta

señal()

public final void signal() {
    
    
   // 如果当前线程不是持有该锁的线程.抛出异常
   if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //获得第一个节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

Despierta de la cabeza

private void doSignal(Node first) {
    
    
   do {
    
    
   		//第一个节点的下一个节点为null
   		//将lastWaiter变为nul
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

Usando un bucle do while, primero determine el siguiente nodo del nodo actual y luego ejecutetransferForSignal (nodo) método

transferForSignal (nodo)

final boolean transferForSignal(Node node) {
    
    
    //如果CAS将状态值变为0失败,则返回false,继续循环,类似自旋
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

   //将节点node加到AQS队列
   //返回node的前一个节点
    Node p = enq(node);
    int ws = p.waitStatus;
    //CAS将前一个节点状态置为SIGNAL状态,唤醒当前线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

Finalmente usadoLockSupport.unpark (node.thread) Método para despertar el hilo en el nodo nodo

Supongo que te gusta

Origin blog.csdn.net/xzw12138/article/details/106469832
Recomendado
Clasificación