A través del análisis en el artículo anterior, sabemos que hay tres formas de adquirir cerraduras con el modo exclusivo, a saber, para obtener interrupciones de hilo de respuesta, para obtener interrupciones de hilos de respuesta y obtener tiempo de tiempo de espera. También hay estas tres formas de adquirir cerraduras en modo compartido, y son básicamente las mismas. Si descubrimos de una manera, podemos entender rápidamente otras formas. Aunque el código fuente del Synchronizer abstractqueed tiene más de mil líneas, también se repite muchas veces, por lo que los lectores no deben tener miedo al principio. Simplemente léelo con paciencia y lentamente, naturalmente lo entenderá gradualmente. En mi experiencia personal, hay varios aspectos más críticos que comprender al leer el código fuente del Synchronizer abstractueedEeded, a saber, la diferencia entre el modo exclusivo y el modo compartido, el estado de espera de los nodos y la comprensión de las colas condicionales. Si comprende estos puntos clave, la lectura del código fuente posterior será mucho más fácil. Por supuesto, estos se introducen en mi artículo "Java Concurrency Series [1] ---- Análisis del código fuente del SynChronizer abstractueed", y los lectores pueden consultarlo primero. Este artículo analiza el modo de intercambio en tres formas de adquirir cerraduras y una forma de liberar cerraduras.
1. No responder a la adquisición de interrupción de hilo
// adquirir el bloqueo en modo no interruptible (modo compartido) público Final void adquirido (int arg) {// 1. Intente adquirir el bloqueo if (tryAcquireshared (arg) <0) {// 2. Si la adquisición falla, ingrese este método doacquireshared (arg); }} // intente adquirir el bloqueo (modo compartido) // Número negativo: indica que la adquisición falló // valor cero: indica que el nodo actual se adquiere con éxito, pero el nodo sucesor ya no puede obtener // número positivo: indica que el nodo actual se adquiere con éxito, y el nodo sucesor (nodo);Llamar al método adquirido es una forma de adquirir el bloqueo sin responder a las interrupciones de los subprocesos. En este método, TryAcquireshared se llama primero para intentar adquirir el bloqueo. El método TryAcquireshared devuelve un estado de adquisición del bloqueo. Aquí AQS especifica que si el estado de retorno es negativo, significa que el nodo actual no adquiere el bloqueo. Si 0 significa que el nodo actual adquiere el bloqueo, pero el nodo posterior ya no se puede adquirir. Si es positivo, significa que el nodo actual adquiere el bloqueo, y los nodos posteriores de este bloqueo también se pueden obtener con éxito. Cuando una subclase implementa la lógica de obtener bloqueos por el método Tryacquireshared, el valor de retorno debe cumplir con esta convención. Si el valor de retorno de llamar a Tryacquireshared es inferior a 0, significa que el intento de adquirir el bloqueo falló. A continuación, llame al método Doacquireshared para agregar el hilo actual a la cola de sincronización. Vemos el método Doacquireshared.
// get (modo compartido) en la cola de sincronización privada void doacquireshared (int arg) {// Agregar al nodo final de la cola final de sincronización = addwaiter (node.shared); booleano fallido = verdadero; intente {boolean interrumped = false; for (;;) {// Obtenga el nodo directo del nodo del nodo actual p = node.pedecesor (); // Si el nodo directo es un nodo principal, intente adquirir el bloqueo nuevamente si (p == head) {// intente adquirir el bloqueo nuevamente y devuelva el estado de adquisición // r <0, lo que indica que la adquisición falló // r = 0, indicando que el nodo actual se adquiere con éxito, pero el nodo posterior no puede adquirir más. adquirido con éxito int r = tryacquireshared (arg); if (r> = 0) {// Para este fin, indica que el nodo actual ha adquirido con éxito el bloqueo. En este momento, propagará la información de estado de bloqueo al nodo posterior setheadandpropagate (nodo, r); p.next = nulo; // Si se recibe una solicitud de interrupción durante el bloqueo de subprocesos, responda a la solicitud en este paso if (Interrupted) {SelfInterrupt (); } fallido = falso; devolver; }} // Cada vez que falla la adquisición de bloqueo, determinará si el hilo se puede suspender. Si es posible, el hilo se suspenderá en el método ParkandCheckInterrupt if (debería ParkAfterFailedAcquire (P, Node) && ParkandCheckInterrupt ()) {Interrupted = True; }}} finalmente {if (fallido) {cancelacquire (nodo); }}}Al ingresar al método Doacquireshared primero, llame al método AddWaiter para envolver el hilo actual en un nodo y colóquelo al final de la cola de sincronización. Hemos hablado sobre el proceso de agregar nodos cuando hablamos sobre el modo exclusivo, por lo que no hablaré de eso aquí. Después de que un nodo ingresa a la cola de sincronización, si encuentra que el nodo frente a él es el nodo de la cabeza, porque el hilo del nodo de la cabeza ha adquirido el bloqueo y ha entrado en la habitación, entonces es su turno adquirir la cerradura. Por lo tanto, el nodo actual no se cuelgará primero, sino que intentará adquirir el bloqueo nuevamente. Si la persona al frente solo libera el bloqueo y se va, el nodo actual puede obtener con éxito el bloqueo. Si la persona en el frente no ha lanzado el bloqueo, llamará al método debería serparkafterfailedacquire. En este método, el estado del nodo principal se cambiará a la señal. Solo asegurando que el estado del nodo anterior sea señal, el nodo actual puede colgar con confianza. Todos los hilos se suspenderán en el método ParkandCheckinterrupt. Si el nodo actual adquiere con éxito el bloqueo, se llamará al método SetheadAndPropagate para establecerse como el nodo de la cabeza y despertar el nodo que también está compartido detrás. Echemos un vistazo a la operación específica del método setheadandpropagate.
// Establezca el nodo head y propague el estado del bloqueo (modo compartido) vacío privado setheadandpropagate (nodo de nodo, int propagate) {nodo h = head; // Establecer el nodo dado como el nodo del cabezal (nodo); // Si el propagado es mayor que 0, significa que el bloqueo puede obtener si (propagate> 0 || h == null || h.waitstatus <0) {// Obtener el nodo sucesor del nodo dado s = nodo.next; // Si el nodo sucesor del nodo dado está vacío, o su estado es un estado compartido si (s == null || s.isshared ()) {// despertar el nodo sucesor doreleasshared (); }}} // Operación de bloqueo de liberación (modo compartido) Private void doreleasshared () {for (;;) {// Obtenga el nodo principal del nodo de cola sincrónica H = head; if (h! = null && h! = tail) {// Obtenga el estado de espera del nodo principal int ws = h.waitStatus; // Si el estado del nodo de la cabeza es señal, significa que alguien está haciendo cola detrás de if (ws == node.signal) {// obtiene el estado de espera del nodo head a 0 if (! Compareandsetwaitstatus (h, node.signal, 0)) {continuar; } // despertar el nodo sucesor UnparkSuccessor (H); // Si el estado del nodo principal es 0, significa que nadie está haciendo cola más tarde, simplemente modifique el estado de la cabeza para propagarse} else if (ws == 0 &&! CompareandSetwaitStatus (h, 0, node.propagate)) {continuar; }} // Solo asegurando que el nodo principal no se haya modificado durante el período, puede salir del bucle si (h == head) {break; }}}Llamar al método SetheadandPropagate primero se establece como el nodo principal, y luego decide si despertar el nodo sucesor en función del valor de retorno del método de tryacquiresheded aprobado. Como se mencionó anteriormente, cuando el valor de retorno es mayor que 0, significa que el nodo actual ha adquirido con éxito el bloqueo, y el nodo posterior también puede adquirir con éxito el bloqueo. En este momento, el nodo actual debe despertar el nodo que también está en el modo compartido. Tenga en cuenta que cada vez que se despierta, es solo para despertar el siguiente nodo. Si el último nodo no está en el modo compartido, el nodo actual ingresará directamente a la habitación y no despertará el nodo adicional. La operación de despertar nodos sucesores en modo compartido se realiza en el método de salas de dorelebras. Las operaciones de activación del modo compartido y el modo exclusivo son básicamente las mismas. Ambos encuentran la marca en su asiento (estado de espera). Si la marca es señal, significa que alguien necesita ayudar a despertarla más tarde. Si la marca es 0, significa que nadie está haciendo cola en la cola en este momento. En modo exclusivo, si encuentra que nadie está haciendo cola, dejará la cola directamente. En el modo compartido, si encuentra que nadie está haciendo cola detrás de la cola, el nodo actual aún dejará una pequeña nota antes de irse (establecer el estado de espera para propagarse) para decirle a las personas posteriores el estado disponible de este bloqueo. Luego, cuando la persona que viene más tarde puede juzgar si adquirir directamente el bloqueo en función de este estado.
2. Respuesta a la adquisición de interrupción de hilos
// Adquirir el bloqueo en modo interrumpible (modo compartido) público Final void adquirido de interrupción (int arg) lanza interruptedException {// Determinar primero si el hilo está interrumpido, de ser así, arroje una excepción if (horthinterrupted ()) {lanzar una nueva interrupción (); } // 1. Intente adquirir el bloqueo if (tryAcquireshared (arg) <0) {// 2. Si la adquisición falla, ingrese este método doacquiresharedInterruptiblemente (arg); }} // adquirir en modo interruptible (modo compartido) privado doacquiresharedInterruptible (int arg) arroja interruptedException {// inserte el nodo actual en la cola del nodo final de la cola de sincronización = addWaIter (node.shared); booleano fallido = verdadero; Pruebe {for (;;) {// Obtenga el nodo final anterior P = node.pedecesor (); if (p == head) {int r = tryacquireshared (arg); if (r> = 0) {setheadandpropagate (nodo, r); p.next = nulo; fallido = falso; devolver; }} if (deber (deberíaParkAfterFailedAcquire (P, nodo) && ParkandCheckInterrupt ()) {// Si el hilo recibe una solicitud de interrupción durante el proceso de bloqueo, inmediatamente lanzará una excepción aquí, lanzará una nueva interrupción, EXCEPTION (); }}} finalmente {if (fallido) {cancelacquire (nodo); }}}La forma de adquirir un bloqueo en respuesta a las interrupciones de los subprocesos y la forma de adquirir un bloqueo en respuesta a las interrupciones de los subprocesos es básicamente el mismo en el proceso. La única diferencia es dónde responder a las solicitudes de interrupción del hilo. Cuando la interrupción del hilo no responde a la interrupción del hilo para adquirir el bloqueo, el hilo se despierta del método ParkandCheckInterrupt. Después de la activación, devuelve inmediatamente si se ha recibido la solicitud de interrupción. Incluso si se recibe la solicitud de interrupción, continuará girando hasta que se haya adquirido hasta que responda a la solicitud de interrupción y se cuelga. El hilo responderá inmediatamente a la solicitud de interrupción después de que se despierta el hilo. Si la interrupción del hilo se recibe durante el proceso de bloqueo, se arrojará inmediatamente una Excepción InterruptedException.
3. Establezca el tiempo de tiempo de espera para obtener
// adquirir el bloqueo con un tiempo de espera limitado (modo compartido) público booleano final tryacquiresharednanos (int arg, long nanostimeut) arroja interruptedException {if (thread.interrupted ()) {lanzar nueva interrupciónxception (); } // 1. Llamar a TryAcquireshared para tratar de adquirir el bloqueo // 2. Si la adquisición falla, llame a Doacquireshednanos Devuelve Tryacquireshared (arg)> = 0 || Doacquiresharednanos (arg, nanoTimeout);} // adquirir el bloqueo con un tiempo de espera limitado (modo compartido) booleano privado doacquireshednanos (int arg, long nanostimeut) lanza interruptedException {long lasttime = system.nanotime (); nodo final nodo = addWaIter (node.shared); booleano fallido = verdadero; Pruebe {for (;;) {// Obtenga el nodo anterior del nodo del nodo actual P = node.pedecesor (); if (p == head) {int r = tryacquireshared (arg); if (r> = 0) {setheadandpropagate (nodo, r); p.next = nulo; fallido = falso; devolver verdadero; }} // Si se usa el tiempo de espera, la adquisición se terminará y la información de falla se devolverá si (nanoStimeOut <= 0) {return false; } // 1. Verifique si se cumple el requisito de suspensión de subproces (garantizado que el estado del nodo directo es la señal) // 2. Compruebe si el tiempo de tiempo de espera es mayor que el tiempo de giro si (debería tener una tartaza de cola (P, nodo) && nanoStimeOut> spinfortimeutthreshold) {// Si se cumplen las dos condiciones anteriores, el hilo actual se suspenderá por un período de tiempo bloqueado. } long Now = System.nanotime (); // El tiempo de tiempo de espera cada vez resta la hora de la adquisición de bloqueo NanoStimeOut - = ahora - LastTime; LastTime = ahora; // Si se recibe una solicitud de interrupción durante el bloqueo, una excepción se lanzará inmediatamente si (Thread.interrupted ()) {Throw New InterruptedException (); }}} finalmente {if (fallido) {cancelacquire (nodo); }}}Si comprende los dos métodos de adquisición anteriores, será muy fácil establecer el método de adquisición de tiempo de tiempo de espera. El proceso básico es el mismo, principalmente para comprender el mecanismo de tiempo de espera. Si el bloqueo se adquiere por primera vez, se llamará al método Doacquiresharednanos y se pasará el tiempo de tiempo de espera. Después de ingresar el método, el bloqueo se adquirirá nuevamente de acuerdo con la situación. Si el bloqueo falla nuevamente, el hilo debe considerarse suspendido. En este momento, determinaremos si el tiempo de tiempo de espera es mayor que el tiempo de giro. Si es así, el hilo se suspenderá por un período de tiempo. De lo contrario, continuaremos tratando de obtenerlo. Después de cada vez que adquiramos el bloqueo, restamos el tiempo de la cerradura para adquirirlo. Nos iremos así hasta que se agote el tiempo de tiempo de espera. Si el bloqueo no se ha adquirido, la adquisición se terminará y se devolverá el indicador de falla de adquisición. El hilo responde a las interrupciones de hilo durante todo el período.
4. Operaciones de descuento de nodos en modo compartido
// Operación del bloqueo de bloqueo (modo compartido) Public Boolean ReleaseShared (int arg) {//1.try para liberar el bloqueo if (tryreleasshared (arg)) {// 2. Si el lanzamiento es exitoso, despierta otros hilos doreleasshared (); devolver verdadero; } return false;} // intente liberar el bloqueo (modo compartido) protegido boolean TryreleaseSshared (int arg) {tire nuevo UnspportedOperationException ();} // Operación de bloqueo de bloqueo (modo compartido) privado void doreleaseSared () {para (;;) {// Obtenga el nodo de la cabeza del nodo sincrónico h = head; Head; Head; if (h! = null && h! = tail) {// Obtenga el estado de espera del nodo principal int ws = h.waitStatus; // Si el estado del nodo de la cabeza es señal, significa que alguien está haciendo cola más tarde si (ws == node.signal) {// primero actualiza el estado de espera del nodo head a 0 if (! Compareandsetwaitstatus (h, node.signal, 0)) {continuar; } // despertar el nodo posterior UnkSuccessor (H); // Si el estado del nodo de la cabeza es 0, significa que nadie está haciendo cola más tarde, solo cambia el estado de la cabeza para propagarse} else if (ws == 0 &&! CompareandSetwaitStatus (h, 0, node.propagate)) {continuar; }} // El bucle solo se puede romper si (h == head) {break; }}}Después de que el hilo finalice funcione en la habitación, llamará al método de desallado para liberar el bloqueo. Primero, llamará al método Tryreleasasshared para intentar liberar el bloqueo. La lógica del juicio de este método es implementada por la subclase. Si el lanzamiento es exitoso, llame al método Doreleasshared para despertar el nodo sucesor. Después de salir de la habitación, encontrará el asiento original (nodo principal) y verá si alguien ha dejado pequeñas notas en el asiento (señal de estado). Si es así, despierta el nodo sucesor. Si no hay (estado 0) significa que nadie está haciendo cola en la cola, entonces lo último que tiene que hacer antes de irse es irse, lo que es dejar una pequeña nota en su asiento (el estado está listo para propagar) para decirle a las personas detrás de la cerradura que adquiran el estado. La única diferencia entre todo el proceso de liberación de bloqueo y el modo exclusivo es funcionar en este último paso.
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.