Galería de mapas mentales Herramienta de bloqueo de concurrencia de Java
Este es un mapa mental sobre las herramientas de bloqueo de concurrencia de Java. Estas herramientas de bloqueo de concurrencia pueden desempeñar un papel importante en la programación concurrente de Java y ayudarlo a escribir código concurrente eficiente y seguro.
Editado a las 2024-01-18 10:28:15,Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Herramienta de bloqueo de concurrencia de Java
ResumenQueueSynchronizer
Introducción
El marco básico para construir cerraduras u otros componentes de sincronización, utilizado principalmente para administrar el estado de sincronización.
El uso principal es herencia.
El bloqueo solo puede ocurrir en un momento, lo que reduce el costo del cambio de contexto.
heredar
ResumenOwnableSynchronizer
Implementación básica de sincronizador que permite que los subprocesos ocupen el modo exclusivo.
Variables miembro
Hilo transitorio privado exclusivoOwnerThread
El hilo que actualmente ocupa el estado de sincronización.
método principal
set de vacío final protegidoExclusiveOwnerThread (hilo de hilo)
Establecer hilo exclusivo
Hilo final protegido getExclusiveOwnerThread()
Obtenga el hilo exclusivo actual
implementar interfaz
Serializable
clase interna
Nodo de clase final estático
Introducción
AQS utiliza internamente una cola de sincronización FIFO bidireccional para completar la gestión del estado de sincronización. Cuando un hilo no se puede obtener, se agrega al final de la cola. La estructura de la cola es Nodo, se definen los nodos principal y final. en AQS. La cola de condiciones también usa la definición de Node, pero está en otra cola.
Variables miembro
constante estática
Nodo final estático COMPARTIDO = nuevo Nodo()
Indica que el nodo está en modo compartido.
Nodo final estático EXCLUSIVO = nulo
Indica que el nodo está en modo exclusivo y solo un subproceso mantiene el estado de sincronización al mismo tiempo.
int final estático CANCELADO = 1
El nodo (hilo) se encuentra en este estado debido a un tiempo de espera o una interrupción, y no cambiará a otros estados posteriormente ni deberá bloquearse más.
estática final int SEÑAL = -1
Cuando el nodo (subproceso) está en este estado, el nodo sucesor está o estará en un estado bloqueado (mediante estacionamiento), por lo que cuando el nodo actual libera el bloqueo o se cancela, se debe llamar a desestacionar para despertar el nodo sucesor. .
estática final int CONDICIÓN = -2
Indica que el nodo actual (hilo) está en la cola de condiciones (mediante espera) y no forma parte de la cola de sincronización hasta que el estado se establece en 0 en un momento determinado (mediante señal)
int final estático PROPAGAR = -3
Propagar bloqueos compartidos (utilizados por el nodo principal)
estado de espera int volátil
Incluyendo lo anterior CANCELADO/SEÑAL/CONDICIÓN/PROPAGAR y 0 (que indica que no está en estos cuatro estados), un número no negativo indica que el nodo no requiere interacción
Nodo volátil anterior
nodo predecesor
Nodo volátil siguiente
nodo sucesor
hilo volátil
El hilo correspondiente al nodo.
Nodo siguienteCamarero
En la cola de sincronización, indica si el nodo está en estado compartido o exclusivo, igual a COMPARTIDO para indicar compartir y nulo para indicar exclusivo, en la cola condicional, indica una referencia al siguiente nodo.
La condición solo se puede utilizar cuando la cola de sincronización está en modo exclusivo, por lo que esta variable está diseñada para ser compartida.
método principal
Método de construcción
Nodo()
Se utiliza para establecer nodos iniciales o construir nodos COMPARTIDOS.
Nodo (hilo de hilo, modo nodo)
Para usar en addWaiter
Nodo (hilo de hilo, int estado de espera)
Usado en condiciones
método miembro
booleano final isShared()
volver siguienteCamarero == COMPARTIDO
predecesor del nodo final()
Obtenga el nodo predecesor y genere una excepción cuando sea nulo
Objeto de condición de clase pública
Introducción
La implementación de la interfaz Condition sirve para la implementación básica de Lock. Utilice Node para construir una cola de espera. Cada objeto de condición contiene una cola FIFO (unidireccional).
La cola utiliza la propiedad nextWaiter de Node como referencia al siguiente nodo.
implementar interfaz
Condición
Serializable
Variables miembro
constante estática
int final estático privado REINTERRUMPIR = 1
Indica la necesidad de interrumpir nuevamente al salir esperar
int final estático privado THROW_IE = -1
Indica que se debe lanzar InterruptedException al salir de la espera
Nodo transitorio privado firstWaiter
Apunta al nodo principal de la cola condicional.
Nodo transitorio privado lastWaiter
Apunta al nodo final de la cola de condiciones.
método principal
espera de anulación final pública()
Haga que el hilo que llama ingrese a la cola de espera, libere el bloqueo y entre en el estado de bloqueo. Después de que el nodo sea despertado por signal/signAll, intente adquirir el bloqueo. Si el bloqueo se despierta por una interrupción, responderá a la interrupción.
Nodo privado addConditionWaiter()
Agregue un nuevo nodo a la cola de espera.
Lógica de implementación específica: cuando se descubre que lastWaiter está cancelado, llame a unlinkCancelledWaiters para borrar los métodos cancelados en toda la cola (tanto firstWaiter como lastWaiter pueden apuntar a nuevos nodos y luego crear un nuevo nodo (estado CONDICIÓN) y agregarlo al; final de la cola
desvinculación nula privadaCancelledWaiters()
Utilice while para borrar los nodos cuyo estado de espera no sea CONDICIÓN desde primerCamarero en adelante. Dado que la llamada al método se realiza antes de que se libere el bloqueo, no es necesario bloquear el proceso del bucle. Para evitar residuos en el GC, cuando no hay señal, este método solo se utiliza cuando se agota el tiempo de espera o se cancela.
El estado de espera del nodo en la cola de condiciones solo debe tener dos estados: CONDICIÓN y CANCELADO.
final intfullyRelease (nodo nodo)
Liberar el estado de bloqueo Si no se puede liberar el estado de bloqueo, se generará una IllegalMonitorStateException y el nodo se establecerá en el estado CANCELADO. Si tiene éxito, se devolverá el valor del estado antes de la liberación.
liberar
Aquí se llama al método de liberación de AQS y se encuentra un nodo adecuado para activar.
Los parámetros utilizan el atributo de estado de AQS.
booleano final isOnSyncQueue (nodo nodo)
Determine si el nodo está en la cola de sincronización (tenga en cuenta que esta es una cola de sincronización en lugar de una cola condicional): cuando el estado de espera es CONDICIÓN, significa que no está en la cola de sincronización si el nodo anterior o siguiente está en la cola de sincronización. nulo, también significa que no está en la cola de sincronización (estos dos atributos se usan para la cola de sincronización. El predecesor y el sucesor de la cola condicional no usan estos dos atributos), y luego llame al método findNodeFromTail para atravesar la sincronización haga cola desde el nodo de cola para ver si el nodo está en él.
La relación entre la cola de sincronización y la cola de condiciones: la competencia de bloqueos solo se basa en la cola de sincronización. La cola de condiciones solo guarda los subprocesos bloqueados debido a la falta de condiciones. Cuando los subprocesos necesitan competir por los bloqueos, aún deben convertirse en una cola de sincronización.
Este método se utiliza como condición de juicio de while en el código. Si el nodo no está en la cola de sincronización (lo que indica que no es signal/signalAll), ingresará al while y bloqueará el estacionamiento. Cuando se despierta el nodo, primero determinará si fue despertado por una interrupción. De lo contrario, continuará regresando al proceso while. De lo contrario, intentará agregar el nodo a la cola de sincronización y salir del proceso while. proceso.
adquirirQueued(nodo, estadoguardado)
Intentar constantemente obtener el estado de sincronización de estado guardado para los nodos en la cola de sincronización
desvincularCanceladoCamareros
Después de adquirir el bloqueo, si node.nextWaiter no es nulo, llame a este método para borrar el nodo en el estado cancelado.
informarinterrumpirdespuésesperar
Si se despertó debido a una interrupción anterior, la interrupción debe procesarse en función de los resultados del juicio anterior, ya sea para restablecer el estado de la interrupción o generar una excepción de interrupción.
larga espera final pública Nanos (largo nanosTimeout)
La lógica básica es similar a la de espera, con la adición del juicio de tiempo de espera.
No entiendo muy bien la última parte de transferAfterCancelledWait.
espera booleana final pública (mucho tiempo, unidad TimeUnit)
La lógica básica es similar a awaitNanos, puede especificar la unidad de tiempo y, finalmente, se convierte a nanosegundos para el cálculo.
El vacío final público espera ininterrumpidamente ()
La lógica básica es similar a esperar y no responde a interrupciones (es decir, selfInterrupt restablece el estado de interrupción)
espera booleana final pública hasta (fecha límite)
La lógica básica es similar a la de espera, con la adición de un tiempo máximo de bloqueo.
señal de anulación final pública()
Mueva el hilo de espera más largo de la cola condicional a la cola de sincronización
booleano protegido isHeldExclusively()
Devuelve si el hilo actual mantiene el bloqueo en modo exclusivo; de ser así, devuelve verdadero. Este método está en AQS y no proporciona una implementación específica. Sin embargo, dado que este método solo se usa en condiciones, no es necesario implementarlo si no se usa ConditionObject; de lo contrario, es necesario implementarlo.
doSignal vacío privado (nodo primero)
Elimine los nodos de la cola de condiciones y conviértalos en nodos de la cola de sincronización. El proceso de implementación es un bucle, de adelante hacia atrás, hasta que encuentra un nodo que no es nulo y no está en estado CANCELADO, lo convierte y sale del bucle.
transferencia booleana final para señal (nodo nodo)
Convierta el nodo de la cola condicional a la cola de sincronización y devuelva si la conversión fue exitosa. La lógica de implementación es determinar primero si el estado del nodo es CONDICIÓN. Si no es un nodo cancelado, devuelve falso y luego llama a enq para ingresar a la cola de sincronización y obtener el nodo predecesor del nodo en la cola. el estado del nodo predecesor es > 0 o CAS se modifica. Si el estado de espera falla (se han producido cambios), desactive el subproceso y finalmente devuelva verdadero
No necesariamente se activa directamente después de unirse a la cola de sincronización. Solo cuando waitStatus>0 o cambia durante la ejecución de CAS el subproceso se desaparcará y se reactivará. Si no se despierta en este momento, esperará hasta que se active la lógica de la cola de sincronización y luego se despertará.
enq
señal de anulación final públicaTodos()
Transfiera todos los nodos calificados (no cancelados) en la cola de condiciones a la cola de sincronización. La lógica básica es similar a la señal, la diferencia es que el método transferForSignal se ejecuta en un bucle.
se lleva a cabo exclusivamente
vacío privado doSignalAll (nodo primero)
transferirParaSeñal
hasWaiters booleano final protegido()
Si hay subprocesos en la cola de condiciones, el método de implementación es realizar un ciclo de la cola de condiciones desde el principio. Si hay un nodo cuyo estado es CONDICIÓN, devuelve verdadero; de lo contrario, devuelve falso.
Variables miembro
variable estática
spinForTimeoutThreshold largo final estático = 1000L;
El umbral utilizado para el tiempo de espera es menor que este valor, no es necesario llamar al estacionamiento con tiempo de espera, pero deja que el ciclo continúe ejecutándose. En este momento, la eficiencia del giro es mayor.
final estático privado Inseguro inseguro
Unsafe.getUnsafe (), lo siguiente se utiliza para obtener el desplazamiento a través del método objectFieldOffset, de modo que las modificaciones posteriores se puedan realizar directamente a través de la operación CAS de Unsafe.
compensación de estado larga final estática privada
estado AQS
headOffset largo final estático privado
jefe de AQS
Offset de cola larga final estático privado
cola AQS
espera larga final estática privadaStatusOffset
Estado de espera del nodo
privado estático final largo nextOffset
Nodo siguiente
Cabeza de nodo volátil transitorio privado
El nodo principal de la cola de sincronización, carga diferida, solo se modifica a través de setHead cuando el nodo principal existe, su estado no se puede CANCELAR;
Cola de nodo volátil transitoria privada
El nodo final de la cola de sincronización, carga diferida
estado int volátil privado
Estado de sincronización
método principal
protegido final int getState()
Devuelve el valor actual del estado de sincronización.
setState vacío final protegido (int newState)
Establecer el estado de sincronización actual
compareAndSetState booleano final protegido (int esperar, int actualización)
CAS establece el estado y la capa inferior llama al método CAS de Unsafe
addWaiter de nodo privado (modo de nodo)
método de cola
Agregue el hilo actual al final de la cola usando el modo especificado
Cabe resaltar que
El parámetro Nodo se utiliza para especificar el modo y el hilo se obtiene a través de currentThread
En la implementación, cuando la cola no está vacía, intente rápidamente con cas configurar la cola. Si tiene éxito, regresará directamente. Si falla, se llamará al método enq.
enq de nodo privado (nodo de nodo final)
Bucle (giro) CAS Cuando la cola está vacía, CAS inicializa la cabeza (nuevo nodo) primero. Cuando la cola no está vacía, CAS establece la cola.
El nodo de parámetro aquí es el nodo que debe agregarse en lugar del modo.
público final booleano hasQueuedPredecessors()
Determine si hay un nodo antes del hilo actual; de ser así, devuelva verdadero; de lo contrario, devuelva falso. Se utiliza para la implementación de cerraduras de feria. Implementación específica: cuando la cola no está vacía, devuelve verdadero si se considera que el nodo sucesor del encabezado está vacío o no el hilo actual.
¿Cuándo es head.next nulo?
hasQueuedThreads booleano final público()
return head != tail; si hay subprocesos esperando en la cola
público final booleano isQueued (hilo de hilo)
Ya sea que el hilo esté en la cola, la implementación es realizar un bucle desde el final hasta el principio.
público final int getQueueLength()
Obtenga la longitud de la cola, que se implementa haciendo un bucle desde el final hasta el principio para calcular el número de nodos cuyo hilo no es nulo.
Colección final pública<Thread> getQueuedThreads()
Obtiene la colección de subprocesos en la cola y la devuelve en forma de Colección. El método de implementación es construir un ArrayList, realizar un bucle de principio a fin para agregar subprocesos que no sean nulos y luego devolver el objeto ArrayList.
propiedad booleana final pública (condición de objeto de condición)
Si el objeto de condición pertenece al sincronizador actual AQS
modo exclusivo
Solo un hilo puede obtener el estado de sincronización al mismo tiempo
adquisición pública final nula (int arg)
El modo exclusivo obtiene el estado de sincronización e ignora las interrupciones; primero llame a tryAcquire para intentar adquirir el bloqueo exclusivo y, después del error, llame a adquirirQueued(addWaiter(Node.EXCLUSIVE), arg) para intentar agregar el subproceso a la cola de sincronización y determinar el estado del nodo y luego, según el resultado devuelto (si hay (interrupción del subproceso) Llame al método de interrupción del subproceso para restaurar el estado de interrupción
La adquisición en sí no responde a las interrupciones, por lo que el procesamiento de las interrupciones es restaurar el estado de la interrupción.
tryAcquire booleano protegido (int arg)
Intente obtener un bloqueo exclusivo, devuelva verdadero si tiene éxito y falso si falla. La implementación específica se completa mediante subclases y se debe garantizar la seguridad de los subprocesos al obtener el estado de sincronización.
adquisición booleana final en cola (nodo de nodo final, int arg)
arg puede entenderse como un recurso que debe competirse. AQS está representado internamente por estado, pero de hecho el uso específico lo define el desarrollador.
Intente continuamente adquirir bloqueos para los subprocesos que ya están en la cola (el nodo ha sido creado por addWaiter)
Lógica de implementación: gire de la siguiente manera: si el nodo predecesor está en cabeza y adquiere el bloqueo con éxito, configure el nodo actual en cabeza y el retorno no se interrumpe; de lo contrario, continúe con las siguientes operaciones: llame a mustParkAfterFailedAcquire para determinar y configurar el estado del nodo; Llame a parkAndCheckInterrupt para bloquear el hilo y verificar el estado de la interrupción. Si ocurre una excepción durante el proceso, llame a cancelAcquire para cancelar la adquisición.
De hecho, incluso si no se adquiere el bloqueo, el bucle no se detendrá. Cuando el estado del nodo predecesor se establece correctamente y el subproceso se interrumpe con el estacionamiento, el subproceso se suspende; El nodo después del encabezado usará tryAcquire para competir con el nuevo hilo por el bloqueo, lo que indica que la implementación del bloqueo aquí es injusta.
setHead vacío privado (nodo nodo)
Configure el nodo en encabezado y establezca los atributos de subproceso y anterior en nulos para facilitar la GC. De hecho, equivale a salir de la cola desde el principio de la cola.
booleano estático privado deberíaParkAfterFailedAcquire (Nodo pred, Nodo nodo)
Verifique y actualice el estado del nodo predecesor del nodo que no pudo adquirir el bloqueo a SIGNAL y devuelva si es necesario bloquear el subproceso actual.
Lógica de implementación: determine el estado de espera del nodo predecesor y devuelva verdadero cuando sea SEÑAL cuando> 0, significa que el nodo predecesor ha sido cancelado y el nodo predecesor se omitirá en un bucle hasta el estado de espera de un nodo predecesor; <= 0, y luego devuelve falso; de lo contrario, CAS establece el estado de espera del nodo predecesor en SEÑAL y devuelve falso;
parkAndCheckInterrupt() booleano final privado
Estacionar llamada, verificar el estado de la interrupción y regresar
return thread.interrupted() El resultado devuelto aquí se utiliza para determinar si el hilo se despertó debido a una interrupción. Si se despertó debido a una interrupción, todavía estará en el bucle en adquirirQueued y será bloqueado nuevamente por park (. debido a que el método interrumpido () borrará el indicador de interrupción, por lo que el estacionamiento puede tener efecto nuevamente cuando se despierte al desbloquearlo, el bloqueo se adquirirá cuando se pseudodesperte, simplemente verifique nuevamente a través del bucle externo;
¿Qué causa la falsa excitación?
cancelación de vacío privadoAdquirir (nodo nodo)
Se llama cuando todo el método genera una excepción, vacía el subproceso del nodo, establece el estado en CANCELADO y busca el nodo con waitStatus<=0 como precursor, elimínate de la cola y activa el siguiente si es necesario. un nodo adecuado
vacío privado unparkSuccessor (nodo nodo)
Cabe señalar que si el siguiente del nodo no está vacío, desactívelo; si el siguiente del nodo es nulo, comience desde la cola y busque el nodo más frontal con waitStatus<=0 que no sea el nodo desde la cola hasta el final. el frente.
La razón por la que buscamos de atrás hacia adelante es porque configurar el predecesor y el sucesor de una lista doblemente enlazada no es una operación atómica. Puede suceder que el siguiente de un nodo esté vacío pero ya esté en la cola, o el nodo acaba de terminar. ha sido cancelado y su siguiente apunta a sí mismo, y el recorrido simplemente llega a este nodo.
adquisición anulada final pública de forma interrumpible (int arg)
Para obtener el estado de sincronización en modo exclusivo en respuesta a una interrupción, se utilizará Thread.interrupted() para determinar si se interrumpe antes de tryAcqure. Si es así, se generará una excepción.
intentar adquirir
pruébalo una vez primero
vacío privado doAcquireInterruptiblemente (int arg)
La lógica básica es la misma que adquirirQueued. La diferencia es que addWaiter se llama internamente y no devuelve si hay una interrupción del subproceso, sino que arroja directamente una InterruptedException después de verificar que la interrupción despierta el parque.
cancelarAdquirir
tryAcquireNanos final público booleano (int arg, long nanosTimeout)
Adquisición exclusiva del estado de sincronización con tiempo de espera, respondiendo a interrupciones. Si no se obtiene el estado de sincronización dentro del período de tiempo de espera, se devuelve falso.
intentar adquirir
pruébalo una vez primero
booleano privado doAcquireNanos(int arg, long nanosTimeout)
La lógica básica es la misma que doAcquireInterruptiblemente, con la adición de lógica de juicio de tiempo y el uso del umbral spinForTimeoutThreshold.
cancelarAdquirir
lanzamiento booleano final público (int arg)
El modo exclusivo libera el estado de sincronización. Primero, se llama a tryRelease para intentar modificar el estado. Después del éxito, se juzga que head! = null y head.waitStatus! = 0 (el estado de espera de head aquí es 0, lo que indica una cola vacía cuando el estado de espera de head). es 0, significa una cola vacía. Cuando se llama, se llama a unparkSuccessor para activar los nodos posteriores y luego devuelve verdadero. tryRelease falla y devuelve falso.
tryRelease booleano protegido (int arg)
Intente modificar el estado. No existe una implementación específica en AQS y las subclases deben implementarla por sí mismas. La variable arg del método de liberación se utiliza para este método y el valor de retorno es si el estado se liberó correctamente;
Para obtener una descripción de arg, consulte la sección de implementación a continuación.
desaparcarSucesor
Modo de uso compartido
Varios subprocesos obtienen el estado de sincronización al mismo tiempo
adquisición de anulación final pública compartida (int arg)
El modo compartido adquiere el estado de sincronización e ignora las interrupciones. Implementación: primero llame a tryAcquireShared para intentar obtener el estado de sincronización. Después del error (el resultado es menor que 0), llame a doAcquireShared para girar y obtener el estado de sincronización.
protegido int tryAcquireShared (int arg)
Intente obtener el estado de sincronización. No se proporciona ningún método específico en AQS y se implementa mediante subclases. Un valor de retorno menor que 0 indica que la adquisición falló; igual a 0 indica que la adquisición fue exitosa, pero ninguna otra adquisición en modo compartido fue exitosa mayor que 0 indica que la adquisición fue exitosa y otras adquisiciones en modo compartido también pueden ser exitosas.
vacío privado doAcquireShared (int arg)
Después de unirse a la cola, spin intenta obtener el estado de sincronización. La lógica básica es básicamente la misma que la de adquirirQueued en modo exclusivo. La diferencia es que cuando el predecesor del nodo es head, el resultado devuelto por tryAcquireShared debe juzgarse. es mayor o igual a 0 (lo que indica que todavía hay recursos, puede continuar propagándose), luego llame a setHeadAndPropagate para configurar los atributos de encabezado y propagación (establezca un nuevo encabezado y juzgue el nodo sucesor, y actívelo si corresponde) de lo contrario, aún debe llamar a mustParkAfterFailedAcquire y parkAndCheckInterrupt para operaciones posteriores (estacionamiento y juicio de interrupción posterior, etc.). Además, en este método también se ejecuta el método selfTninterrupt para configurar el estado de interrupción.
intentar adquirir compartido
Cuando el predecesor del nodo sea el principal, pruébelo primero.
setHeadAndPropagate vacío privado (nodo nodo, propagación int)
La encarnación de la comunicación. Lo que este método realmente verifica es el nodo sucesor del nodo que actualmente adquiere el bloqueo, es decir, se puede propagar hacia atrás una vez. Al principio, me preguntaba por qué no encontré ningún código similar a despertarse hacia atrás en un bucle. Más tarde descubrí que setHeadAndPropagate está en for(;;). , entonces Esto logra el propósito de la propagación hacia atrás uno por uno. Tenga en cuenta que no se despiertan juntos y luego compiten, se despiertan uno por uno.
Continúe despertando los nodos hacia atrás. Este método se llamará solo cuando tryAcquireShared devuelva el resultado r>=0 (lo que indica que todavía hay recursos disponibles) y r se pasa al método como parámetro de propagación. Este método configurará el nodo como encabezado y luego determinará si se puede pasar hacia atrás. Si es así, llame a doReleaseShared para activar el nodo siguiente.
Lógica de juicio: propagar>0 (indica que hay permiso) O encabezado anterior == nulo O head.waitStatus anterior < 0 O el encabezado actual (es decir, nodo) == nulo O ahora head.waitStatus < 0 Luego vaya al siguiente paso: nodo.siguiente == nulo o nodo.next.isShared()
En realidad no está muy claro: 1. ¿Por qué necesitamos determinar la cabeza actual ((h = cabeza) == nulo) 2. ¿Por qué todavía se requiere doReleaseShared después de node.next == null?
La especulación de 1: debido a que otros subprocesos pueden modificar el encabezado durante el proceso de juicio, si el nuevo encabezado aún cumple con esta condición, aún puede continuar, siento que la lógica de juicio aquí es más bien no sé por qué el encabezado es nulo; pero todavía lo intento. Una estrategia de procesamiento conservadora, entonces 2 puede ser una estrategia conservadora similar.
Solo se considera que waitStatus <0 se debe a que el estado del cabezal puede cambiar a SEÑAL y CONDICIÓN no aparecerá aquí.
vacío privado doReleaseShared()
Spin activa el nodo sucesor, que se diferencia del modo independiente en que también necesita garantizar las propiedades de propagación. Implementación: Bucle: cuando la cola no está vacía, determine el estado de espera del encabezado. Cuando es igual a SEÑAL, CAS establece el estado de espera en 0. Si tiene éxito, se llamará a unparkSuccessor (cabeza) para activar el nodo sucesor. , comience desde el principio; waitStatus es 0 y CAS establece waitStatus en 0. Cuando PROPAGATE falla, comience de nuevo y luego juzgue head==h para ver si el nodo principal se ha modificado y, de ser así, salga del ciclo.
Tenga en cuenta que este método no pasa parámetros y comienza directamente desde el encabezado.
Especulación: el nodo de modo compartido solo puede tener tres estados: 0 (recién creado), PROPAGAR y SEÑAL.
desaparcarSucesor
cancelarAdquirir
adquisición de anulación final pública compartida interrumpidamente (int arg)
El modo compartido obtiene el estado de sincronización y admite interrupciones de manera similar al modo exclusivo.
intentar adquirir compartido
vacío privado doAcquireSharedInterruptiblemente (int arg)
Similar a la lógica doAcquireShared, se lanza una excepción después de que se detecta una interrupción
cancelarAdquirir
tryAcquireSharedNanos final público (int arg, long nanosTimeout)
El modo compartido con período de tiempo de espera obtiene el estado de sincronización y admite interrupción.
intentar adquirir compartido
booleano privado doAcquireSharedNanos(int arg, long nanosTimeout)
La lógica de juicio de tiempo de espera es similar al modo exclusivo y otra lógica de ejecución es similar a doAcquireShared.
cancelarAdquirir
lanzamiento booleano final público compartido (int arg)
El modo compartido libera el estado de sincronización
tryReleaseShared booleano protegido (int arg)
El método vacío, implementado por subclases, intenta liberar el estado de sincronización. arg es el parámetro de releaseShared y usted mismo puede definirlo; el valor de retorno es verdadero cuando este método libera con éxito el estado de sincronización y otros subprocesos pueden adquirirlo; de lo contrario, devuelve falso.
hacerReleaseShared
Aquí solo despertamos un nodo posterior, y la propagación del despertar posterior la lleva a cabo el nodo despertado.
Cómo implementar tu propio bloqueo basado en AQS
recursos competitivos
AQS utiliza el estado de la variable privada para representar de forma abstracta los recursos para la competencia de bloqueo (por ejemplo, 1 significa ocupado, 0 significa no ocupado)
Las operaciones sobre el estado requieren el uso de métodos relevantes: getState, setState, compareAndSetState, etc.
Método de implementación bajo demanda
tryAcquire, tryAcquireShared, tryRelease, tryReleaseShared, isHeldExclusively
El parámetro arg de varios métodos en AQS finalmente se pasa a estos métodos, por lo que estos métodos determinan si el parámetro arg es necesario y cómo usarlo. Este es el significado de "puede representar cualquier cosa" en los comentarios del código fuente.
Bloqueo exclusivo (modo independiente)
tryAcquire booleano protegido (int arg);
La implementación de consultas y la obtención de recursos competitivos generalmente se completa mediante la operación CAS del estado. Los parámetros entrantes pueden ser definidos por el desarrollador.
Ejemplo sencillo: tryAcquire booleano protegido (int adquiere) { afirmar adquiere == 1; // Definir que el parámetro entrante solo puede ser 1 si (compareAndSetState(0, 1)) { devolver verdadero; } falso retorno; } tryRelease booleano protegido (lanzamientos int) { afirmar lanzamientos == 1; // Definir que el parámetro entrante solo puede ser 1 if (getState() == 0) lanza una nueva IllegalMonitorStateException(); establecerEstado(0); devolver verdadero; }
tryRelease booleano protegido (int arg);
Para liberar recursos competitivos, normalmente la variable de estado se reduce o se pone a cero. Los parámetros entrantes pueden ser definidos por el desarrollador.
No debe haber ningún bloqueo ni espera dentro del método.
bloqueo compartido
protegido int tryAcquireShared(int adquiere)
Los parámetros entrantes los define el desarrollador y pueden considerarse como la cantidad de recursos que desean obtener; el valor de retorno int puede considerarse como la cantidad restante de recursos compartidos.
Ejemplo sencillo: protegido int tryAcquireShared(int adquiere) {//no justo para (;;) { int disponible = getState(); int restante = disponible - adquiere; if (restante < 0 || compareAndSetState(disponible, restante)) retorno restante; } } tryReleaseShared booleano protegido (versiones int) { para (;;) { int actual = getState(); int next = versiones actuales; si (compareAndSetState (actual, siguiente)) devolver verdadero; } }
tryReleaseShared booleano protegido (versiones int)
Los propios desarrolladores definen los parámetros entrantes y pueden considerarse como la cantidad de recursos que desean liberar.
variable de condición
Aunque la lógica de implementación de AQS no usa el estado, cuando se usan variables de condición, el estado se usará como parámetro entrante de liberación (int arg) de forma predeterminada, por lo que a menudo es necesario modificar la variable de estado al implementar estos métodos.
booleano protegido isHeldExclusively()
Devuelve si el hilo actual ocupa exclusivamente el bloqueo mutex correspondiente a la variable de condición
Métodos de implementación comunes: booleano protegido isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } tryAcquire booleano protegido (int adquiere) { ... setExclusiveOwnerThread(Thread.currentThread()); devolver verdadero; }
Bloqueo de lectura y escritura reentrante
Bloqueo reentrante
Introducción
Los bloqueos reentrantes se dividen en dos métodos: bloqueos justos y bloqueos injustos.
implementar interfaz
Cerrar
Serializable
clase interna
sincronización de clases estáticas abstractas
La clase abstracta es la base para la implementación de ReetrantLock
heredar
ResumenQueuedSynchronizer
método principal
bloqueo vacío abstracto()
¿Es un método abstracto para adquirir un candado? ¿Es necesario que lo implemente una subclase?
final booleano no justoTryAcquire (int adquiere)
Implementación injusta de tryLock La implementación específica es: CAS adquiere el bloqueo cuando ningún subproceso adquiere actualmente el bloqueo. Si tiene éxito, el subproceso exclusivo del bloqueo se establece en el subproceso actual, si el subproceso actual ya contiene el bloqueo, se actualiza el número de bloqueos actuales. En estos dos casos, se devolverá verdadero, lo que indica que el bloqueo se mantiene, y en otros casos se devolverá falso.
Entonces, ¿por qué se colocaría una implementación injusta en la clase principal Sync? Debido a que este método se utiliza para bloqueos justos e injustos, los tryLocks de ambos se basan en esta implementación.
tryRelease booleano final protegido (versiones int)
La implementación de tryRelease en AQS es calcular primero el número total de bloqueos menos el número de bloqueos liberados para obtener el número de bloqueos retenidos restantes c. Si c es 0, borre el estado en el valor de c y devuelva. si no hay cerraduras retenidas.
booleano final protegido isHeldExclusively()
isHeldExclusively implementado en AQS, devuelve getExclusiveOwnerThread() == Thread.currentThread()
final int getHoldCount()
Devuelve cuántos bloqueos contiene el hilo actual. Este método se basa en el juicio de isHeldExclusively
booleano final está bloqueado ()
return getState() != 0; si un hilo mantiene el bloqueo
hilo final getOwner()
Devuelve el hilo que contiene el candado, o nulo si no
objeto de condición final nueva condición()
nuevoObjetoCondición()
objeto de lectura vacío privado (java.io.ObjectInputStream s)
Método de deserialización, establezca el estado en 0
clase final estática FairSync
Clase de implementación de bloqueo justo
heredar
Sincronizar
método principal
bloqueo de vacío final()
adquirir(1)
El parámetro 1 aquí tiene un significado práctico, que es diferente de CountDownLatch.
tryAcquire booleano final protegido (int adquiere)
Pruebe adquirir la implementación de bloqueo justo. El proceso de implementación es: cuando el bloqueo ocupado c es 0, si no hay ningún subproceso al frente y cas establece con éxito c en adquisición, entonces configura el subproceso actual como un subproceso exclusivo y devuelve verdadero si el subproceso actual ya contiene el bloqueo; actualiza el número de bloqueos y devuelve verdadero; devuelve falso en todos los demás casos.
tiene predecesores en cola
Aquí hay más juicio que un bloqueo injusto. Incluso si ningún hilo adquiere el bloqueo actualmente, si hay un hilo esperando por más tiempo, abandonará la adquisición del bloqueo.
clase final estática NonfairSync
Clase de implementación de bloqueo injusto
heredar
Sincronizar
método principal
bloqueo de vacío final()
Primero intente con cas (0,1) directo. Si tiene éxito, significa que nadie más está adquiriendo el bloqueo actualmente. El hilo actual tomó el bloqueo con éxito y luego configure el hilo exclusivo como el hilo actual, si falla; (1)
De hecho, el método tryAcquire del bloqueo injusto también probará cas. El cas directo aquí puede resaltar que las llamadas anteriores pueden obtener recursos antes.
tryAcquire booleano final protegido (int adquiere)
devolver no justoTryAcquire(adquiere) La implementación injusta ya existe en Sync, llámala directamente
Variables miembro
sincronización final privada
Indica si el bloqueo se implementa de manera justa o injusta y corresponde a FairSync o NonfairSync después de la creación de instancias.
Constructor
bloqueo reentrante público()
Cerradura injusta construida por defecto
ReentrantLock público (justo booleano)
Construya un candado justo o un candado injusto según la elección justa
método principal
bloqueo de vacío público ()
sincronización.lock()
bloqueo de vacío público de forma interrumpible ()
sincronizar.adquirir de forma interrumpible (1)
tryLock booleano público()
return sync.nonfairTryAcquire(1); si el bloqueo se adquiere con éxito o el hilo actual ya lo mantiene, devuelve verdadero; de lo contrario, devuelve falso.
No importa cuál sea el candado aquí, es un intento injusto de adquirirlo.
tryLock booleano público (tiempo de espera prolongado, unidad TimeUnit)
devolver sync.tryAcquireNanos(1, unidad.toNanos(tiempo de espera))
tryAcquireNano llama a tryAcquire, por lo que existe una diferencia entre justicia e injusticia.
desbloqueo de vacío público()
sincronización.liberación(1)
Condición pública nuevaCondición()
devolver sincronización.nuevaCondición()
público int getHoldCount()
return sync.getHoldCount() El número de bloqueos retenidos por el hilo actual
booleano público isHeldByCurrentThread()
devolver sync.isHeldExclusively() si el hilo actual mantiene el bloqueo
público booleano está bloqueado ()
devolver sincronización.isLocked()
público final booleano isFair()
devolver instancia de sincronización de FairSync
Hilo protegido getOwner()
devolver sincronización.getOwner()
hasQueuedThreads booleano final público()
devolver sync.hasQueuedThreads() si hay hilos en la cola
hasQueuedThread booleano final público (hilo de hilo)
return sync.isQueued(thread); si el hilo actual está en la cola
público final int getQueueLength()
devolver sincronización.getQueueLength();
Colección protegida<Thread> getQueuedThreads()
devolver sincronización.getQueuedThreads();
hasWaiters público booleano (Condición de condición)
Si hay subprocesos esperando una condición dada (correspondiente a si hay subprocesos en estado CONDICIÓN en la cola de condiciones)
public int getWaitQueueLength(Condición de condición)
El número de subprocesos en el estado CONDICIÓN en la cola de condiciones para una condición determinada.
Colección protegida <Subproceso> getWaitingThreads (Condición de condición)
Condición
Introducción
Las variables de condición de interfaz se utilizan principalmente para gestionar la dependencia de la ejecución del hilo en ciertos estados, asignar los métodos de monitoreo del objeto (esperar/notificar/notificar a todos) a objetos directos y combinarlos con cualquier implementación de bloqueo para lograr cada objeto. el efecto de múltiples conjuntos de espera. Es equivalente a que Lock reemplace la sincronización y que la Condición reemplace el método del monitor, es decir, la Condición debe usarse junto con el Bloqueo.
Las principales diferencias entre los métodos de condición y monitor son: 1. La condición admite múltiples colas de espera y una instancia de bloqueo se puede vincular a múltiples condiciones. 2. La condición puede admitir configuraciones de interrupción y tiempo de espera de respuesta
El significado profundo de vincular múltiples condiciones es que los subprocesos se pueden administrar de acuerdo con diferentes situaciones. El llamado control "más fino" significa que los subprocesos bloqueados debido a diferentes condiciones se encuentran en diferentes colas de condiciones y solo se activarán cuando sea necesario. Entrar en la cola de sincronización para competir, en sí mismo diferencia y aísla.
entender
La diferencia con la competencia de bloqueo general es que la condición enfatiza las condiciones. En un entorno de subprocesos múltiples, se deben cumplir las condiciones antes de que se puedan realizar ciertas operaciones, mientras que la competencia de bloqueo general equivale simplemente a competir por el derecho a ejecutar un determinado fragmento de código; .
Modelo de programación habitual: Obtener bloqueo; while (el estado condicional no se cumple) { desbloquear el bloqueo; El hilo se cuelga y espera hasta que se cumpla la condición para la notificación; Volver a adquirir la cerradura; } Operaciones de sección crítica; desbloquear el bloqueo;
Relación ternaria: bloqueo, espera, afirmación condicional (predicado). Cada llamada de espera está implícitamente asociada con una aserción condicional específica; cuando se llama a esperar para una aserción condicional específica, la persona que llama ya debe mantener el bloqueo de la cola condicional relacionada con la aserción, y este bloqueo debe proteger las condiciones que constituyen la aserción condicional. Variables de estado
predicado: una función que devuelve tipo bool
crear
Creada a través del método newCondition de la interfaz Lock, la clase que debe implementarse implementa su propia lógica (esencialmente creando una instancia de ConditionObject)
método principal
espera vacía()
Deje que el subproceso espere hasta que se despierte mediante una señal o una interrupción cuando se le llame, el bloqueo asociado con la condición se liberará automáticamente y el subproceso dejará de ejecutar la tarea actual hasta que se despierte mediante una señal/señalTodo/interrupción/activación espuria. El hilo debe volver a intentar adquirir el bloqueo asociado con la condición antes de llamar al método de espera.
espera booleana (mucho tiempo, unidad TimeUnit)
Similar a awaitNanos, puede especificar la unidad de tiempo y devolver falso cuando se agote el tiempo; de lo contrario, devolver verdadero
larga espera Nanos (largo nanosTimeout)
Similar a await, pero se activará a más tardar después de la hora especificada y devolverá el tiempo no utilizado.
vacío aguarda ininterrumpidamente();
Similar a await, pero no responde a interrupciones.
booleano aguardar hasta (fecha límite)
Similar a awaitNanos, la forma de especificar la hora es diferente. Si se alcanza la fecha límite, devuelve falso; de lo contrario, devuelve verdadero.
señal nula()
Activar un hilo en estado de espera Una vez que el hilo se activa, necesita adquirir el bloqueo nuevamente.
señal nulaTodos()
Despierta todos los hilos en espera
Cerrar
Introducción
La interfaz proporciona algunos métodos comunes para solicitar/liberar bloqueos.
Comparación con sincronizado
Los mecanismos relacionados de sincronización (como monitorear objetos, etc.) están integrados y no se puede acceder a ellos a nivel de idioma. Lock es una solución alternativa a nivel de idioma, por lo que puede obtener algunas comodidades a nivel de idioma (como conocer). si el bloqueo se adquirió con éxito, etc.)
El proceso de bloqueo y desbloqueo de sincronizado se completa con la palabra clave en sí, mientras que Lock necesita llamar explícitamente al método de bloqueo y desbloqueo.
Lock puede usar variables de condición para usar bloqueos de manera más flexible
El bloqueo puede responder a las interrupciones, pero sincronizado no
método principal
bloqueo vacío()
bloqueo vacío de forma interrumpible ()
Se responderá a las interrupciones durante el proceso de solicitud de bloqueo.
tryLock booleano()
Intente adquirir el bloqueo y devolver el resultado directamente (sin bloquear el hilo actual)
tryLock booleano (largo tiempo, unidad TimeUnit)
tryLock con tiempo de espera e interrupción de respuesta
desbloqueo nulo();
Condición nuevaCondición()
Devuelve un objeto Condición asociado con el bloqueo.
BloqueoSoporte
Introducción
Primitivas básicas de bloqueo de subprocesos (relacionadas con permisos) utilizadas para crear bloqueos y otras clases de sincronización
La capa inferior se basa en una implementación insegura.
Método de construcción
Privado, no se permite la construcción de objetos de esta clase.
Variables miembro
privado estático final sun.misc.Unsafe INSEGURO
Construir en bloque de código estático
sun.misc.Inseguro
parque largo final estático privadoBlockerOffset
Obtenga la posición de desplazamiento del atributo parkBlocker en el objeto de clase Thread a través de Unsafe
parkBlocker es un registro cuando el hilo está bloqueado, lo que facilita las herramientas de monitoreo y diagnóstico para identificar el motivo por el cual el hilo está bloqueado; es nulo cuando el hilo no está bloqueado;
SEMILLA larga final estática privada
Obtenga la posición de desplazamiento de la propiedad threadLocalRandomSeed en la clase Thread a través de Unsafe
SONDA larga final estática privada
Obtenga la posición de desplazamiento de la propiedad threadLocalRandomProbe en la clase Thread a través de Unsafe
privado estático final largo SECUNDARIO
Obtenga la posición de desplazamiento de la propiedad threadLocalRandomSecondarySeed en la clase Thread a través de Unsafe
Estas tres propiedades las establece ThreadLocalRandom
método principal
Objeto estático público getBlocker (Subproceso t)
Obtenga el objeto parkBlocker en el objeto Thread a través de Unsafe
setBlocker vacío estático privado (hilo t, objeto arg)
Método privado, configure el objeto parkBlocker en Thread a través de Inseguro, los siguientes métodos para configurar el bloqueador llaman a este método
parque público vacío estático()
Bloquee el hilo y espere "permiso" para continuar la ejecución.
Implementación específica (Hotspot): clase Parker
具体的实现与平台相关,每个线程都有一个Parker实例,在linux实现中实际上是以mutex和condition最终实现了锁和阻塞的过程(具体看笔记文章,这里不写了)
主要成员变量(linux实现)
private volatile int _counter
表示许可,大于0表示已获取许可,每次park只会将其设置为1,不会递增
Por lo tanto, llamar a desaparcar dos veces y luego llamar a estacionar dos veces seguirá bloqueando la segunda vez.
protected pthread_mutex_t _mutex[1]
互斥变量
在代码中可以通过linux原子指令pthread_mutex_lock、pthread_mutex_trylock、pthread_mutex_unlock对变量进行加锁和解锁操作
protected pthread_cond_t _cond[1]
条件变量
利用线程间共享的全局变量进行同步的一种机制,一般和互斥锁结合使用(避免条件变量的竞争);可以通过pthread_cond_wait、pthread_cond_timedwait等指令进行操作
También existe una cola de espera en el kernel de Linux para gestionar procesos bloqueados.
方法
park
unpark
Diferencias de uso de esperar
esperar debe esperar la notificación antes de poder activarse. Hay un proceso secuencial y el desestacionamiento que está esperando se puede llamar antes de estacionar. En este momento, el programa después del estacionamiento puede continuar ejecutándose.
esperar necesita obtener el monitor del objeto, pero estacionar no
Aviso
Si se produce una interrupción en el estacionamiento, el estacionamiento también regresará pero no generará una excepción de interrupción.
parque público de vacíos estáticos (bloqueador de objetos)
Debido a la función de parkBlocker, se recomienda más utilizar el método park con parámetros de bloqueo. Después de usarlo, puede ver el mensaje sobre en qué objeto está bloqueado el hilo a través de herramientas de diagnóstico como jstack, como el estacionamiento para esperar. <0x000000045ed12298> (un objeto xxxx con nombre completo)
parque público de vacío estático Nanos (bloqueador de objetos, nanos largos)
parque público de vacío estático Nanos (nanos largos)
El tiempo máximo de bloqueo antes de que el programa continúe ejecutándose.
parque público de vacío estático hasta (bloqueador de objetos, fecha límite larga)
parque público de vacío estático hasta (plazo largo)
desestacionamiento de vacío estático público (hilo de rosca)