Eche un vistazo más de cerca al código fuente de Condition en AQS

Siga recto, hoy solo quiero hablar sobre el código fuente de ConditionObject, no muchos chismes. La palabra ConditionObject puede parecer un poco extraña, pero siempre estás familiarizado con Condition, ¿verdad? En ReentrantLock, condition.await () y condition.signal () se utilizan generalmente para realizar el mecanismo de espera y activación del bloqueo.

Primero veamos un ejemplo simple, puede ser un dolor de cabeza mirar directamente el código fuente.

public class WaitTest {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {

        // 线程一
        Thread thread1 = new Thread(() -> {
            try {
                lock.lock();
                //to do sth
                System.out.println("线程一开始");
                condition.await();
                System.out.println("线程一等待后继续运行");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread1");
        //线程二
        Thread thread2 = new Thread(() -> {
            try {
                //线程二等待下,让线程一先开始
                Thread.sleep(20);
                lock.lock();
                //获取锁,再等待,线程一已经进入等待队列,只能等线程二
                Thread.sleep(200);
                //to do sth
                System.out.println("线程二开始");
                //唤醒了线程一,但是没有释放锁,即使释放锁了,线程一抢占时间片也需要时间,未必有线程二先执行完
                condition.signal();
                System.out.println("线程二等待后继续运行");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread2");

        thread1.start();
        thread2.start();
    }
}

Eche un vistazo a los resultados de la ejecución:

线程一开始
线程二开始
线程二等待后继续运行
线程一等待后继续运行

Echemos un vistazo simple a este programa, una función principal que crea dos subprocesos asincrónicos, usa el bloqueo exclusivo ReentrantLock y usa el mecanismo de condición de espera para despertar en ReentrantLock al mismo tiempo.

En primer lugar, el hilo uno y el hilo dos se apoderan de la cerradura. Por supuesto, el hilo dos duerme por un tiempo corto, lo que equivale a renunciar al derecho de usar la cerradura y solo se puede bloquear en la cola de sincronización.

Luego, tan pronto como el hilo espera, ingresa a la cola de espera y renuncia a la propiedad del bloqueo.

El hilo dos se adelanta al bloqueo y comienza a ejecutarse hacia abajo.

El subproceso dos luego llama a condition.signal () para despertar al subproceso uno en la cola de espera. El subproceso uno ingresa a la cola de sincronización desde la cola de espera. Sin embargo, ¡el bloqueo todavía está ocupado por el subproceso dos ! El hilo uno solo puede seguir esperando.

Por supuesto, incluso si el hilo dos libera el control del bloqueo, después de condition.signal (), ejecuta lock.unlock (), el hilo dos se ejecutará antes que el hilo 1. Después de todo, el hilo uno se mueve a la cola de sincronización y toma el bloqueo. Lleva tiempo y el hilo dos pasa directamente.

Otra cosa a mencionar es que después de llamar a condition.signal (), ¡no significa que el hilo despertado se pueda ejecutar! Simplemente coloque el hilo en la cola de sincronización, está listo y listo. En este momento, el subproceso todavía necesita obtener el control del bloqueo , es decir, finalmente debe asignarse al segmento de tiempo antes de que pueda ejecutarse. Esto requiere que todos le presten atención. El subproceso en Java se puede ejecutar inmediatamente después de que no se despierte!

Con respecto a la condición, tenemos que averiguar dos pasos:

1. Suelte el candado y entre en la cola de espera

2. Active el bloqueo, salga de la cola de espera y vuelva a mover el hilo a la cola de sincronización.

Echemos un vistazo al código fuente:

 /* lock 锁等待方法 */ 
 public final void await() throws InterruptedException {
			//线程是中断的,没法等待,抛异常
            if (Thread.interrupted())
                throw new InterruptedException();
			// 新建一个节点,并添加到等待队列中,这个时候是占有lock锁的
            Node node = addConditionWaiter();
			// 释放当前线程占有的lock
            int savedState = fullyRelease(node);
            int interruptMode = 0;
			//是否在同步队列中
            while (!isOnSyncQueue(node)) {
				//阻塞当前
                LockSupport.park(this);
				//checkInterruptWhileWaiting(node)) 不等于0,跳出,这个是唤醒之后的操作。
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
			// 自旋等待获取到同步状态(即获取到lock)
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
		

La pieza de código anterior es la lógica principal de la espera de espera, hablemos sobre el mecanismo principal de espera para despertarse encerrado. En primer lugar, debemos saber que si el nodo del hilo se mueve a la cola de espera, este hilo debe mantener el bloqueo en este momento.

Veamos el primer paso. El código tiene una explicación. Permítanme hablar de ello. Primero, el hilo actual tiene un bloqueo. El hilo actual crea un nuevo nodo y lo agrega al final de la cola de espera. Esta cola de espera es un FIFO en espera Después de que la cola se agrega a la cola, el bloqueo se libera y la ocupación de recursos finaliza. Pero en este momento, el hilo no se bloquea directamente, sino que continúa ejecutándose, pero los recursos bloqueados se han liberado. Podemos ver que FullyRelease (nodo); todavía tiene código ejecutándose a continuación.

Luego se ejecuta un ciclo while. Como es un ciclo while, definitivamente saltará. Es imposible hacer un ciclo infinito, ¿verdad? En primer lugar, si la condición while es falsa, naturalmente no se volverá a ejecutar y hay un signo de "ruptura" en el cuerpo del bucle. Si se ejecuta la ruptura, ¡saltará naturalmente!

Es la primera vez que se juzga si está en la cola de sincronización (! IsOnSyncQueue (nodo)). No debe estar allí. Acaba de mudarse, ¿dónde está? Luego ingrese el cuerpo del bucle, ejecute LockSupport.park (this); y luego se atasque. En este momento, el primer paso está completamente completado ~

Echemos un vistazo a algunos de los códigos fuente que contiene.

¡la primera! Agregue un nuevo nodo a la cola de espera.

  private Node addConditionWaiter() {
			//将lastWaiter赋值给t
            Node t = lastWaiter;
            // 节点在等待队列上,但是状态又不是Node.CONDITION,那状态就是CANCELLED(1)了,
			// 已经取消调度,直接可以清除了
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
			//根据当前进程,新建一个节点。
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
		

Esto no es difícil, solo agregue un nodo a la cola de espera, creo que todos pueden entenderlo, (* ^ ▽ ^ *). Déjame publicar una de las llamadas dentro

	 /* 取消已取消的服务程序节点 */
   private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
			//首节点不为空,进入循环,就是为了干掉所有滴
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

El siguiente es un fragmento de código más crítico, suelte el bloqueo

 final int fullyRelease(Node node) {
		 //失败标志
        boolean failed = true;
        try {
			// 获取当前线程的state
            int savedState = getState();
			//释放锁,调用的lock.release
            if (release(savedState)) {
				// 释放成功,返回state
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
			// 释放失败,那就直接让这个线程节点跪了呗。
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

Este código es para ceder el control de la cerradura, que es similar al código que libera la cerradura en la cerradura. El primero es obtener el estado del hilo y luego liberarlo. Después de que se ejecuta el lanzamiento, el control del bloqueo se abandona. El siguiente código es un procesamiento del hilo actual. Después de regresar, eventualmente se ejecutará para LockSupport. Park (esto); Bloquea el hilo actual.

De esta forma, básicamente podemos conocer un proceso de espera: establecer un nodo en espera --- "entrar en la cola de espera ---" liberar el bloqueo --- "bloquear en espera

Hablemos del segundo paso, señal-despertar.

Para despertar el hilo, tenemos que aclarar dos puntos: Primero, el hilo actual debe obtener el bloqueo antes de que pueda ser despertado. En segundo lugar, no se puede especificar la activación. La cola de espera es un FIFO, y solo se puede despertar el hilo que ingresa primero a la cola de espera.

Bien, echemos un vistazo al código.

	// 唤醒最早进入等待队列的线程
	 public final void signal() {
			// 是否拥有锁,没有就抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
			// 获取第一个节点
            Node first = firstWaiter;
            if (first != null)
				// 不为空就唤醒
                doSignal(first);
        }
	
	   // 是否独占锁,公平锁的实现方式
	   protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

El código fuente anterior no es nada para eludir. Las operaciones de activación están todas en doSignal (primero); en esta operación, entremos y echemos un vistazo.

	 // 唤醒等待线程
	  private void doSignal(Node first) {
            do {
				// 等待队列首节点干掉
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
				
            }
			//成功就加入到同步队列,结束循环,否则就继续找下一个节点
			while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
		
 
    final boolean transferForSignal(Node node) {
        // 重新设置waitStatus,设置失败,说明已经cancel了,会在循环中干掉
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //下面的代码是不是很熟悉?嘿嘿,没错,就是加入到同步队列中去的操作
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

Echemos un vistazo al código de doSignal. Primero, obtenga el primer nodo firstWaiter y elimine este nodo de la cola de espera. Luego, realice la operación transferForSignal. Si la actualización de estado es exitosa, muévase a la cola de sincronización y finalice el ciclo. Si el nodo ha sido cancelado, continúe buscando el siguiente nodo hasta que el primer nodo que todavía está vivo en la cola de espera sea despierto.

¿Qué hay de despertar a todos? De hecho, es muy simple. ¿No se despierta lo anterior y finaliza el ciclo? Ya no estoy fuera del circuito, ¿puedo seguir despertando? \ (^ o ^) / ~, enséñame ~

 // 唤醒所有的等待队列
	  private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
				//移出等待队列,添加到同步队列
                transferForSignal(first);
                first = next;
            } 
			//首节点不为空就一直循环
			while (first != null);
        }

El código central tiene solo unas pocas líneas, ¿es fácil?

En resumen, el hilo retiene el bloqueo ---- "obtener el primer nodo de la cola de espera ----" mover fuera de la cola de espera ---- "agregar a la cola de sincronización

La idea no es difícil de entender. Si el código está implementado, lo he publicado. Si hay un error, puedes señalarlo en los comentarios ~

Además, si es incómodo mirar el código fuente directamente, puede intentar depurar el método principal de mi prueba anterior, y se puede llegar a la lógica principal ~

Sin sacrificio, sin victoria ~

Supongo que te gusta

Origin blog.csdn.net/zsah2011/article/details/111665592
Recomendado
Clasificación