En el artículo anterior "Java Concurrency Series [1] ----- Análisis del código fuente de SynChronizer de abstracción", introdujimos algunos conceptos básicos de StractSqueedSynChronizer, principalmente hablando sobre cómo se implementa el área de la cola de AQS, cuál es el modo exclusivo y el modo compartido, y cómo entender el estado de espera de los nodos. Comprender y dominar estos contenidos es la clave para la lectura posterior del código fuente de AQS, por lo que se recomienda que los lectores lean primero mi artículo anterior y luego miren hacia atrás a este artículo para comprenderlo. En este artículo, presentaremos cómo los nodos ingresan la cola de cola de sincronización en modo exclusivo y qué operaciones se realizarán antes de dejar la cola de sincronización. AQS proporciona tres formas de obtener cerraduras en modo exclusivo y modo compartido: adquisición de interrupción de subprocesos no sensibles, adquisición de interrupción de hilos de respuesta y establecer la adquisición de tiempo de espera. Los pasos generales de estos tres métodos son aproximadamente los mismos, con solo unas pocas partes diferentes, por lo que si comprende un método y observa la implementación de otros métodos, será similar. En este artículo, me centraré en el método de adquisición para no responder a las interrupciones de hilos, y los otros dos métodos también hablarán sobre las inconsistencias.
1. ¿Cómo obtener cerraduras con interrupciones de hilos no respondientes?
// no responde a la adquisición del método de interrupción (modo exclusivo) public Final void adquirir (int arg) {if (! TryAcquire (arg) && adquirequeed (addWaIter (node.exclusive), arg)) {autointerrupt (); }}Aunque el código anterior parece simple, realiza los 4 pasos que se muestran en la figura a continuación en orden. A continuación demostraremos y analizaremos paso a paso.
Paso 1 :! TryAcquire (Arg)
// Intenta adquirir el bloqueo (modo exclusivo) booleano protegido tryacquire (int arg) {tirar nueva no apoyoperationException ();}En este momento, alguien vino e intentó llamar primero a la puerta. Si descubriera que la puerta no estaba cerrada (Tryacquire (arg) = verdadero), entraría directamente. Si encuentra que la puerta está bloqueada (TryAcquire (arg) = falso), realice el siguiente paso. Este método de TryAcquire determina cuándo está abierto el bloqueo y cuándo está cerrado el bloqueo. Este método debe sobrescribirse por subclases y reescribir la lógica del juicio en el interior.
Paso 2: AddwaIter (nodo.exclusive)
// Envuelve el hilo de corriente en un nodo y agrégalo a la cola de la cola de sincronización Node privado Addwaitrer (modo de nodo) {// Especifique el modo que contiene el nodo de bloqueo de nodo = nuevo nodo (shifh.currentThread (), modo); // Obtener la referencia del nodo de cola del nodo de cola de sincronización pred = cola; // Si el nodo de cola no está vacío, significa que la cola de sincronización ya tiene un nodo if (pred! = Null) {// 1. Apunte al nodo del nodo de cola actual.prev = pred; // 2. Establezca el nodo actual en el nodo de cola if (compareSettail (pred, nodo)) {// 3. Apunte al sucesor del nodo de cola antiguo al nuevo nodo de cola pred.next = nodo; nodo de retorno; }} // de lo contrario, significa que la cola de sincronización no se ha inicializado enq (nodo); nodo return;} // nodo enqueue nodo privado enq (nodo del nodo final) {for (;;) {// Obtenga la referencia del nodo de cola del nodo de cola de sincronización t = cola; // Si el nodo de la cola está vacío, significa que la cola de sincronización no se ha inicializado si (t == null) {// inicializa la cola de sincronización if (compareAndsethead (new node ())) {tail = head; }} else {// 1. Apunte al nodo del nodo de cola actual.prev = t; // 2. Establezca el nodo actual en el nodo de cola if (compareSettail (t, nodo)) {// 3. Apunte al sucesor del nodo de cola antiguo al nuevo nodo de cola t.next = nodo; regresar t; }}}}La ejecución a este paso indica que la primera vez que la adquisición de bloqueo falló, por lo que la persona obtendrá una tarjeta numérica para sí mismo y ingresará al área de la cola para cola. Al recibir la tarjeta numérica, declarará cómo quiere ocupar la habitación (modo exclusivo o modo de intercambio). Tenga en cuenta que no se sentó y descansó en este momento (cuelgarse).
Paso 3: adquirir (addwaiter (nodo.exclusive), arg)
// adquirir el bloqueo de una manera ininterrumpida (modo exclusivo) Boolean Boolean adquirir (nodo final nodo, int arg) {boolean fallado = true; intente {boolean interrumped = false; para (;;) {// Obtenga la referencia del nodo anterior del nodo final dado p = node.pedecesor (); // Si el nodo actual es el primer nodo de la cola de sincronización, intente adquirir el bloqueo if (p == head && tryacquire (arg)) {// Establezca el nodo dado como el nodo de cabeza (nodo); // Para ayudar a la recolección de basura, borre el sucesor del nodo principal anterior p.next = null; // Establecer el estado de adquisición exitoso fallido = falso; // Devuelve el estado interrumpido, todo el bucle se ejecuta aquí, la devolución de salida interrumpida; } // De lo contrario, significa que el estado de bloqueo aún no está disponible. En este momento, determine si el hilo actual se puede suspender // si el resultado es verdadero, el hilo actual se puede suspender; de lo contrario, el bucle continuará, durante este período, el hilo no responderá a la interrupción si (debería parkafterfailedAcquire (p, nodo) && parkandcheckinterrupt ()) {interrupta = true; }}} Finalmente {// asegúrese de cancelar la adquisición if (fallida) {cancelacquire (nodo); }}} // juzga si puede suspender el nodo actual el booleano estático privado debería ser compuerta de la medida (nodo pred, nodo nodo) {// Obtener el estado de espera del nodo avanzado int ws = pred.waitstatus; // Si el estado del nodo delantero es señal, significa que el nodo avanzado despertará el nodo actual, por lo que el nodo actual puede suspender de manera segura si (ws == node.signal) {return true; } if (ws> 0) {// La siguiente operación es limpiar todos los nodos delanteros cancelados en la cola de sincronización do {node.prev = pred = pred.prev; } while (preds.waitstatus> 0); pred.next = nodo; } else {// Para este fin, significa que el estado del nodo directo no es señal, y es probable que sea igual a 0. De esta manera, el nodo directo no despertará el nodo actual.//so El nodo actual debe asegurarse de que el estado del nodo directo sea señal para colgarlo de manera segura. } return false;} // Suspender el hilo actual Final Private Boolean ParkandCheckInterrupt () {LockSupport.Park (this); return thread.interrupted ();}Después de obtener el signo de número, implementará inmediatamente este método. Cuando un nodo ingresa al área de la cola por primera vez, hay dos situaciones. Una es que descubre que la persona frente a él ha dejado su asiento y ha entrado en la habitación, por lo que no se sentará y descansará, y volverá a llamar a la puerta para ver si el niño está listo. Si la persona dentro simplemente estuviera terminada, se apresuraba sin llamarse a sí mismo. De lo contrario, tendría que considerar sentarse y descansar por un tiempo, pero todavía estaba preocupado. ¿Qué pasa si nadie le recordó después de que se sentó y se quedó dormido? Dejó una pequeña nota en el asiento del hombre al frente para que la persona que salió desde adentro pudiera despertarlo después de ver la nota. Otra situación es que cuando ingresó al área de la cola y descubrió que había varias personas haciendo cola frente a él, podía sentarse por un tiempo, pero antes de eso, aún dejaría una nota en el asiento de la persona que estaba al frente (ya estaba dormido en este momento) para que la persona pudiera despertarlo antes de irse. Cuando todo está hecho, duerme pacíficamente. Tenga en cuenta que vemos que todo el bucle tiene solo una salida, es decir, solo puede salir después de que el hilo adquiere con éxito el bloqueo. Antes de obtener el bloqueo, siempre se cuelga en el método ParkandCheckInterrupt () del bucle for. Después de que se despierta el hilo, también continúa ejecutando el bucle for desde este lugar.
Paso 4: SelfInterrupt ()
// El hilo actual se interrumpirá a sí mismo el ininterrumpido estático privado () {Thread.CurrentThread (). Interrupt (); }Dado que todo el hilo de arriba se ha colgado en el método ParkandCheckInterrupt () del bucle for, no responde a ninguna forma de interrupción de hilo antes de que se adquiera con éxito. Solo cuando el hilo adquiera con éxito el bloqueo y salga del bucle for For Bucle, verificará si alguien solicita interrumpir el hilo durante este período. Si es así, llame al método SelfInterrupt () para que se cuelgue.
2. ¿Cómo obtener cerraduras en respuesta a interrupciones de hilo?
// Adquirir el bloqueo en modo interrumpible (modo exclusivo) Void privado doacquireinterruptible (int arg) lanza interruptedException {// envolviendo el hilo actual en un nodo y agregándolo al nodo final de la cola de sincronización = addWaIter (node.excluse); booleano fallido = verdadero; Pruebe {for (;;) {// Obtención del nodo final anterior P = node.pedecesor (); // Si P es un nodo de cabeza, entonces el hilo actual intenta adquirir el bloqueo nuevamente si (p == head && tryacquire (arg)) {sethead (nodo); p.next = nulo; // Ayuda GC falló = falso; // Regreso de regreso después de adquirir el bloqueo con éxito; } // Si se cumple la condición, el hilo actual se suspenderá. En este momento, se responde una interrupción y se lanza una excepción si (debería hacer una tartaza de CailedAcquire (P, nodo) && ParkandCheckInterrupt ()) {// Si el hilo se despierta, se lanzará una excepción si se encuentra la solicitud de interrupción, se lanzará una falla. tirar nueva interrupciónxception (); }}} finalmente {if (fallido) {cancelacquire (nodo); }}}El método de interrupción de hilo de respuesta y el método de interrupción de hilo no sensible son más o menos el mismo en el proceso de obtención de bloqueos. La única diferencia es que después de que el hilo se despierta del método ParkandCheckInterrupt, verificará si el hilo está interrumpido. Si es así, lanzará una excepción de InterruptedException. En lugar de responder al bloqueo de adquisición de interrupción de subprocesos, solo establece el estado de interrupción después de recibir la solicitud de interrupción, y no terminará inmediatamente el método actual para adquirir el bloqueo. No decidirá colgarse en función del estado de interrupción después de que el nodo adquiere con éxito el bloqueo.
3. ¿Cómo establecer el tiempo de espera para adquirir la cerradura?
// adquirir el bloqueo con un tiempo de espera limitado (modo exclusivo) Doacquirenanos booleano privado (int Arg, Long NanostimeOut) arroja interruptedException {// Obtener el tiempo actual del sistema durante mucho tiempo = System.nanotime (); // envolver el hilo actual en un nodo y agregarlo al nodo final del nodo final de la cola de sincronización = addWaIter (nodo.exclusive); booleano fallido = verdadero; Pruebe {for (;;) {// Obtención del nodo final anterior P = node.pedecesor (); // Si el nodo anterior es un nodo principal, entonces el hilo actual intenta adquirir el bloqueo nuevamente si (p == head && tryacquire (arg)) {// actualiza el nodo de cabeza sethead (nodo); p.next = nulo; fallido = falso; devolver verdadero; } // Una vez que se usa el tiempo de tiempo de espera, salga del bucle directamente si (nanoStimeOut <= 0) {return false; } // Si el tiempo de tiempo de espera es mayor que el tiempo de giro, luego, después de juzgar que el hilo se puede suspender, el hilo se suspenderá por un período de tiempo si (debería unfilAfterFailedAcquire (p, node) && nanoStimeOut> spinfortimeutthreshold) {// tiene el hilo actual durante un período de tiempo y luego se despierta por sí mismo bloqueado por sí mismo. } // Obtenga la hora actual del sistema Long Now = System.nanotime (); // El tiempo de tiempo de espera se resta del intervalo de tiempo del bloqueo de adquisición nanoTimeOut - = ahora - LastTime; // Actualización de LastTime Again LastTime = ahora; // La excepción se lanza cuando se recibe una solicitud de interrupción durante la adquisición del bloqueo if (hildinterrupted ()) {lanzar nueva interruptaxception (); }}} finalmente {if (fallido) {cancelacquire (nodo); }}}Establecer la adquisición de tiempo de tiempo de espera primero adquirirá el bloqueo. Después de la primera vez que falla la adquisición, se basará en la situación. Si el tiempo de tiempo de espera entrante es mayor que el tiempo de giro, el hilo se suspenderá por un período de tiempo, de lo contrario girará. Después de cada vez que se adquiere la cerradura, se restará el tiempo de tiempo de espera del tiempo necesario para adquirir la cerradura. Hasta que el tiempo de espera sea inferior a 0, significa que el tiempo de tiempo de espera se ha agotado. Luego, se terminará la operación de adquirir el bloqueo y se devolverá el indicador de falla de adquisición. Tenga en cuenta que durante el proceso de adquirir el bloqueo con el tiempo de tiempo de espera, puede responder a las solicitudes de interrupción del hilo.
4. ¿Cómo el hilo libera la cerradura y deja la cola de sincronización?
// Operación de bloqueo de lanzamiento (modo exclusivo) Public Final Boolean Release (int arg) {// gire el bloqueo de contraseña para ver si puede desbloquear if (tryrelease (arg)) {// Obtener el nodo del nodo Head h = head; // Si el nodo de la cabeza no está vacío y el estado de espera no es igual a 0, despierta el nodo sucesor if (h! = Null && h.waitstatus! = 0) {// despertar el nodo sucesor sinksuccessor (h); } return verdadero; } return False;} // despertar el nodo sucesor privado void unparksuccessor (nodo nodo) {// Obtenga el estado de espera del nodo dado int ws = node.waitStatus; // actualiza el estado de espera a 0 if (ws <0) {compareandsetwaitstatus (nodo, ws, 0); } // Obtenga el nodo posterior del nodo de nodo dado s = node.next; // El nodo sucesor está vacío o el estado de espera se cancela si (s == null || s.waitstatus> 0) {s = null; // termina el primer nodo que no se cancela desde la cola de transferencia hacia atrás para (nodo t = cola; t! = Null && t! = Node; t = t.prev) {if (t.waitstatus <= 0) {s = t; }}} // despertar el primer nodo después de un nodo dado que no es un estado de cancelación if (s! = Null) {locksupport.unpark (s.thread); }}Después de que el hilo mantiene la cerradura en la habitación, hará su propio negocio. Una vez realizado el trabajo, liberará la cerradura y saldrá de la habitación. El bloqueo de contraseña se puede desbloquear a través del método de tryrelease. Sabemos que el método de tryrelasee debe ser sobrescribido por la subclase. Las reglas de implementación de diferentes subclases son diferentes, lo que significa que las contraseñas establecidas por diferentes subclases son diferentes. Por ejemplo, en Reentrantlock, cada vez que la persona en la habitación llama al método de tryrelase, el estado se reducirá en 1, hasta que el estado se reduzca a 0, se abrirá el bloqueo de contraseña. Piense en si este proceso parece que estamos constantemente girando la rueda del bloqueo de la contraseña, y el número de ruedas se reduce en 1 cada vez que la giramos. CountdownLatch es un poco similar a este, excepto que no es solo que está cambiando a una persona, sino que va a cambiar a una persona, concentrando la fuerza de todos para abrir la cerradura. Después de que el hilo sale de la habitación, encontrará su asiento original, es decir, encontrar el nodo de la cabeza. Vea si alguien ha dejado una pequeña nota en el asiento. Si lo hay, sabrá que alguien está dormido y necesita pedirle que ayude a despertarlo, y luego despertará ese hilo. Si no, significa que nadie está esperando en la cola de sincronización por el momento, y nadie necesita que se despierte, por lo que puede salir con tranquilidad. El proceso anterior es el proceso de liberar el bloqueo en modo exclusivo.
Nota: Todo el análisis anterior se basa en JDK1.7, y habrá diferencias entre diferentes versiones, los lectores deben prestar atención.
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.