Serie AQS (7) -Final: Resumen de AQS

Prólogo

Este artículo es un resumen de la serie anterior de artículos de AQS, primero mire las siguientes preguntas:

1. ¿Cómo se implementa la función reentrante de ReentrantLock y ReentrantReadWriteLock?

2. ¿Qué variable controla si la cerradura está ocupada?

3. Cuando varios hilos compiten por un bloqueo exclusivo, ¿cómo funciona el hilo que no agarra el bloqueo?

4. ¿Se puede compartir la lectura y la lectura para siempre sin bloquear?

¿Conoces la respuesta a las preguntas anteriores? ¿Están claros todos los principios? En caso afirmativo, no hay necesidad de leer este artículo, de lo contrario, léalo lentamente.

 

Texto

1. Realización de reentrada (para bloqueo exclusivo) -Pregunta 1

    La reentrada se logra a través de exclusiveOwnerThread y state. En la clase primaria AbstractOwnableSynchronizer de AQS, hay una variable miembro exclusiveOwnerThread para almacenar el subproceso cuando se adquiere el bloqueo exclusivo. Esta característica realiza la función reentrante. Si se juzga por el estado de que el bloqueo actual ya está ocupado, se juzga si el hilo actual está almacenado en esta variable. Si es así, el bloqueo se adquiere y el recuento de bloqueo es +1, de lo contrario no se puede adquirir el bloqueo. (Esto es para bloqueos exclusivos, los bloqueos compartidos no pertenecen a esta categoría)

2. El valor del estado en AQS-Pregunta 2

    El estado es un tipo int volátil, que se utiliza para registrar el estado bloqueado y la cantidad de reentradas, pero el método de grabación es diferente para las diferentes clases de implementación.

[ReentrantLock] : el estado se usa para registrar el estado de retención de bloqueo y el número de reingresos. Estado = 0 significa que ningún subproceso retiene el bloqueo; estado = 1 significa que hay un subproceso que retiene el bloqueo; estado = N significa exclusivoOwnerThread este subproceso tiene N reingresos Tengo esta cerradura.

[ReentrantReadWriteLock] : el estado se utiliza para registrar el estado de ocupación del bloqueo de lectura y escritura y el número de subprocesos retenidos (bloqueo de lectura), el número de reentradas (bloqueo de escritura) , los 16 bits superiores del estado registran el número de subprocesos que retienen el bloqueo de lectura y los 16 bits inferiores El número de reingresos de subprocesos de bloqueo de escritura. Si el valor de 16 bits es 0, significa que ningún subproceso ocupa el bloqueo, de lo contrario significa que un subproceso retiene el bloqueo. Además, para bloqueos de lectura, HoldCounter registra el número de bloqueos de lectura adquiridos por cada subproceso en la variable de subproceso local.

[Semáforo] : el estado se usa para contar. state = N significa que hay N semáforos que pueden asignarse, y state = 0 significa que no hay semáforos. En este momento, todos los hilos que requieren adquirir semáforos están esperando;

[CountDownLatch] : el estado también se utiliza para contar . Cada countDown se reduce en uno. Cuando se reduce a 0, se despierta el hilo bloqueado por esperar.

 

3. Sobre el valor de waitStatus en Node

waitStatus es de tipo int volátil y tiene 5 tipos de valores, cuyas funciones son las siguientes:

1: CANCELAR, es decir, el estado cancelado, el nodo en este estado es un nodo no válido y se omitirá directamente durante la ejecución. Generalmente, este estado solo aparecerá en escenarios anormales especiales;

0: estado inicializado, todos los nodos de nodo recién creados están en este estado;

-1: SEÑAL, puede despertarse. Si un nodo no se bloquea, debe ingresar el estado de bloqueo. Primero debe establecer su nodo anterior en -1 antes de ingresar al estado del parque. Busque parkAndCheckInterrupt () en AQS para encontrar que Mientras aparezca este método, debe haber un método shouldParkAfterFailedAcquire (p, nodo) para establecer el estado de espera de p en -1;

-2: CONDITION, el estado relacionado con la cola condicional, no involucrado en ReentrantReadWriteLock y ReentrantLock;

-3: PROPAGATE, que se puede propagar. No veo ningún uso para ello.

 

4. Acerca del nodo principal y el nodo de cola

Cuando el primer subproceso llega a adquirir el bloqueo, la cola no se inicializará, pero la variable exclusiveOwnerThread se cambiará al subproceso actual (en el caso del bloqueo exclusivo), luego la cabeza y la cola son nulas;

El primer subproceso no está terminado, y el segundo subproceso vuelve a aparecer. En este momento, la cola se inicializará, se asignará un nuevo nodo vacío a la cabeza y la cola, y luego el subproceso que se pondrá en cola después de la cola (es decir, la cabeza) (Consulte el método enq a continuación), en este momento se forma una cola bidireccional con dos nodos, la cabeza es un nuevo nodo (), la cola es el nodo recién agregado a la cola; luego, si no se obtiene el bloqueo, el estado de espera de la cabeza se establecerá en -1, se suspende, esperando que unparkSuccessor (cabeza) se despierte.

Método addWaiter: si tail no está vacío, asigne el nodo del nodo actual a tail, y la cola original se convierte en el nodo principal de la nueva cola en la lista vinculada. Se puede ver que la nueva forma de agregar esta lista vinculada es el último tipo de entrada. Esto también explica por qué al despertar el hilo, atravesamos la cola hacia adelante para encontrar el nodo nodo que cumple con los criterios en el frente.

1  nodo privado addWaiter (modo de nodo) {
 2          nodo nodo = nuevo nodo (Thread.currentThread (), modo);
3          // Prueba la ruta rápida de enq; copia de seguridad a enq completo en caso 
de falla 4          Nodo pred = tail;
5          si (pred =! Nulo ) {
 6              node.prev = pred;
7              if (compareAndSetTail (pred, nodo)) {
 8                  pred.next = nodo;
9                  nodo de retorno ;
10              }
 11          }
 12          enq (nodo);
13          nodo de retorno ;
14      }

El código del método enq en la clase AQS es el siguiente: similar a addWaiter, excepto que hay un paso más para inicializar la cabeza / cola.

1 nodo privado enq (nodo de nodo final) { 
 2 para (;;) { 
 3 nodo t = cola; 
 4 if (t == null) {// Debe inicializar 
 5                 if (compareAndSetHead (new Node ())) // initialize El nodo principal, en este momento, el puntero principal apunta al nuevo objeto Nodo () 
 6 tail = head; // La cola también se inicializa juntas, luego continuará pasando por el bucle for nuevamente 
 7  } else {  8 node.prev = t; / / Primero, apunte el puntero anterior del nodo a la variable local t;  9 if (compareAndSetTail (t, node)) {//  Cambie el valor del objeto de la dirección de memoria donde se encuentra la cola al nodo 10 t.next = node; // Cambie el próximo puntero de la cola Señale el nodo nodo, luego el nodo nodo se convierte en el nuevo equipo tail  11 return t; // Regrese el equipo anterior tail t  12 } 13 } 14 } 15 }

 

4. Preguntas sobre el intercambio condicional de bloqueos de lectura-lectura-Pregunta 4

En ReentrantReadWriteLock, el bloqueo de lectura y el bloqueo de lectura no siempre se pueden realizar al mismo tiempo. Si la operación actual es un bloqueo de lectura y la primera cola es un bloqueo de escritura, entonces un nuevo bloqueo de lectura ingresará a la lista vinculada y se bloqueará en lugar de adquirir directamente Bloqueo de lectura, porque si esto no está configurado, puede haber un bloqueo de lectura que se ha estado activando, y el bloqueo de escritura posterior siempre está en un estado bloqueado y no se puede obtener el bloqueo.

Consulte la implementación de bloqueo de lectura injusto del método readShouldBlock -apparentlyFirstQueuedIsExclusive ()

1  booleano final al  parecer FirstFueuedIsExclusive () {
 2          Node h, s;
3          retorno (h = cabeza) =! Nulo &&
 4              (s = h.next) =! Nulo &&
 5              s.isShared () &&!
 6              s.thread =! Nulo ;
7      }

 

5. Métodos de espera / notificación del objeto y LockSupport park / unpark-Question 3

LockSupport se activa y suspende el subproceso según el semáforo directo del subproceso, mientras que esperar / notificar está basado en objetos y debe depender del objeto Monitor.

La semántica de park / unpark es más fácil de entender: park está bloqueando para el hilo actual y unpark está despertando para el hilo especificado, lo que no es contrario al sentido común como esperar / notificar.

Unpark proporciona una licencia para el subproceso especificado, y el subproceso puede continuar ejecutándose con este permiso; park está esperando una licencia. Unpark puede ejecutarse antes del park, el hilo no se bloqueará. Sin embargo, unpark no puede volver a entrar en la pila, y un park también utilizará varios unparks directamente.

En la parte inferior, el método park / unpark de UNSAFE funciona en un objeto Paker (cada subproceso tiene una instancia de Parker). El objeto mantiene una variable _counter. Después de que se llama al método unpark, la variable cambia de 0 a 1, lo que indica que se ha seguido ejecutando Permiso, regrese a 0 después de la ejecución, si la variable no es 1 después de llamar al método de estacionamiento, el subproceso se bloqueará hasta convertirse en 1 y luego se activará para continuar la ejecución.

 

Escribiré sobre esto por el momento, y aprenderé sobre otras herramientas en el paquete JUC más adelante.

Supongo que te gusta

Origin www.cnblogs.com/zzq6032010/p/12076690.html
Recomendado
Clasificación