1. Análisis de código fuente de clase inseguro
La clase insegura en el paquete RT.jar de JDK proporciona operaciones atómicas a nivel de hardware. Los métodos en inseguro son todos métodos nativos, y se accede a las bibliotecas de implementación de C ++ locales utilizando JNI.
Explicación de las funciones principales de la clase insegura en rt.jar. La clase insegura proporciona operaciones atómicas a nivel de hardware y puede operar de forma segura las variables de memoria directamente. Se usa ampliamente en el código fuente JUC. Comprender sus principios establece las bases para estudiar el código fuente de JUC.
Primero, comprendamos el uso de los métodos principales en la clase insegura, de la siguiente manera:
1. Método Long ObjectFieldFoffSet (campo de campo): Devuelve la dirección de compensación de memoria de la variable especificada en la clase a la que pertenece. La dirección de compensación solo se usa al acceder al campo especificado en la función insegura. El siguiente código se usa inseguro para obtener el desplazamiento de memoria del valor variable en AtomicLong en el objeto AtomicLong. El código es el siguiente:
static {try {valueOffset = unsafe.objectFieldOffset (atomicLong.class.getDeclaredField ("valor")); } catch (Exception Ex) {Throw New Error (Ex); }}2. Método de ArrayBaseFoffset (Class ArrayClass): Obtenga la dirección del primer elemento en la matriz
3.int ArrayIndexscale (Class ArrayClass) Método: Obtenga el número de bytes ocupados por un solo elemento en la matriz
3. Método de comparación de comparación (obj object OBJ, desplazamiento largo, esperanza larga, actualización larga): Compare si el valor de la variable del desplazamiento de desplazamiento en el objero obj es igual a esperar. Si es igual, se actualiza con el valor de actualización y luego devuelve verdadero, de lo contrario devolverá falso.
4. Método de GetLongVolative (Obj OBJ, OBJ de objeto): Obtenga el valor de la semántica de memoria volátil correspondiente a la variable del desplazamiento de desplazamiento en el objeco obj.
5. Método de Void PutOrderDLong (Obj OBJ, desplazamiento largo, valor largo): Establezca el valor del campo largo correspondiente a la dirección de desplazamiento de desplazamiento en el objeto OBJ al valor. Este es el método PutLongVolatil con demora, y no garantiza que la modificación del valor sea inmediatamente visible para otros hilos. Las variables solo son útiles si se modifican con volátiles y se espera que se modifiquen inesperadamente.
6. Parque noble (isabsoluta booleano, mucho tiempo) Método: bloquea el hilo actual. Cuando el parámetro isabsolute es igual a falso, el tiempo igual a 0 significa bloquear todo el tiempo. El tiempo mayor que 0 significa que el hilo de bloqueo se despertará después de esperar el tiempo especificado. Esta vez es un valor relativo, un valor incremental, es decir, el hilo actual se despertará después de acumular el tiempo en relación con la hora actual. Si Isabsolute es igual a verdadero y el tiempo es mayor que 0, significa que se despertará después de bloquear al punto de tiempo especificado. Aquí el tiempo es un tiempo absoluto, que es el valor convertido a MS en un punto de tiempo determinado. Además, cuando otros subprocesos llaman al método de interrupción del hilo de bloqueo actual e interrumpen el hilo actual, el hilo actual también volverá. Cuando otros subprocesos llaman al método no tardío y toman el hilo actual como un parámetro, el hilo actual también volverá.
7. Método de No Unark (hilo de objeto): despertar el hilo de bloqueo después de llamar al parque, y los parámetros son los hilos que deben despertarse.
Se han agregado varios métodos nuevos a JDK1.8. Aquí hay una lista simple de los métodos para operar el tipo largo de la siguiente manera:
8. Long getAndsetLong (obj Obj, largo desplazamiento, actualización larga): Obtener el valor de la semántica volátil variable con desplazamiento en el objero obj, y establecer el valor de la semántica volátil variable en actualización. El método de uso es el siguiente:
Public Final Long GetandsetLong (Obj Obj, Long Offset, Long Update) {Long L; do {l = getLongVolatile (obj, offset); // (1)} while (! compareanddswaplong (obj, offset, l, actualización)); regresar l; }Desde el código interno (1), puede usar GetLongVolative para obtener el valor de la variable actual, y luego usar la operación atómica CAS para establecer el nuevo valor. Aquí, el uso de bucles tiene en cuenta la situación en la que múltiples hilos llaman al mismo tiempo, y luego se requiere retres de giro después de que CAS falla.
9. Long getandaddlong (obj obj, desplazamiento largo, largo addValue): obtenga el valor de la semántica de la variable volátil con desplazamiento en el objeto obj, y establezca el valor variable en el valor original + addValue. El método de uso es el siguiente:
Público final Long getandaddlong (obj obj, desplazamiento largo, largo addValue) {long l; do {l = getLongVolatile (obj, offset); } while (! compareanddswaplong (obj, offset, l, l + addValue)); regresar l; }Similar a la implementación de GetAndSetLong, excepto que cuando se usa CAS aquí, se utiliza el valor original + el valor del parámetro incremental transferido addValue.
Entonces, ¿cómo usar la clase insegura?
Ver que inseguro es tan increíble, ¿realmente quieres practicar? Bien, veamos primero el siguiente código:
paquete com.hjc; import sun.misc.unsafe;/*** creado por Cong el 2018/6/6. */public class testUnsafe {// Obtenga la instancia de inseguros (2.2.1) estática final insegura inseguer = unsafe.getUnsafe (); // Registre el valor de desplazamiento del estado variable en la clase TestUnSafe (2.2.2) Static Final Long StateOffset; // Variable (2.2.3) Estado de larga volátil privado = 0; static {try {// Obtener el valor de compensación de la variable de estado en la clase testUnsafe (2.2.4) stateOffset = unsafe.objectFieldOffset (testUnsafe.class.getDeclaredField ("estado")); } catch (excepción ex) {System.out.println (ex.getLocalizedMessage ()); tirar un nuevo error (ex); }} public static void main (string [] args) {// crea una instancia y establece el valor de estado en 1 (2.2.5) testUnsafe test = new testUnsafe (); //(2.2.6) Sucess booleano = unsafe.compareandswapint (test, stateOffset, 0, 1); System.out.println (éxito); }}El código (2.2.1) obtiene una instancia de inseguro, y el código (2.2.3) crea un estado variable inicializado a 0.
El código (2.2.4) usa unsafe.ObjectFieldOffset para obtener la dirección de desplazamiento de memoria de la variable de estado en la clase TestUnSafe en el objeto TestUnSafe y guardarla en la variable StateFoffset.
El código (2.2.6) llama al método de comparación de mando de la instancia insegura creada y establece el valor de la variable de estado del objeto de prueba. Específicamente, si la variable de estado cuya compensación de memoria del objeto de prueba es stateOffset es 0, el valor de actualización se cambia a 1.
Queremos ingresar verdadero en el código anterior, pero después de la ejecución, los siguientes resultados se generarán:
¿Por qué está sucediendo esto? Debe ingresar el código GetunSafe, como ver lo que se hace en él:
Private estático final inseguro TheUnSafe = new Unsafe (); public static inseguer getunSafe () {// (2.2.7) clase localClass = refles.getCallerClass (); // (2.2.8) if (! Vm.issystemdomainloader (localClass.getClassLoader ())) {Throw New SecurityException ("inseguro"); } return theUnSafe;} // Juzgue si ParamClassLoader es un bootstrap Class Loader (2.2.9) public static boolean issystemdomainloader (classloader paramClassLoader) {return paramClassLoader == null; }El código (2.2.7) obtiene el objeto de clase del objeto que llama a GetunSafe, aquí está TestUnsafe.cals.
El código (2.2.8) determina si es el LocalClass cargado por el cargador de clase Bootstrap. La clave aquí es si el cargador de bootstrap carga testUnsafe.class. Aquellos que han visto el mecanismo de carga de clase de Java Virtual Machine ven claramente que se debe a que TestUnsafe.Class se carga utilizando AppClassLoader, por lo que se lanza directamente una excepción aquí.
Entonces, la pregunta es, ¿por qué necesitas hacer este juicio?
Sabemos que la clase insegura se proporciona en rt.jar, y la clase en rt.jar se carga utilizando el cargador de clase Bootstrap. La clase donde iniciamos la función principal se carga utilizando AppClassLoader, por lo que al cargar la clase insegura en la función principal, teniendo en cuenta que el mecanismo de delegación principal delegará a Bootstrap para cargar la clase insegura.
Si no hay autenticación del código (2.2.8), entonces nuestra aplicación puede usar insegura para hacer cosas a voluntad. La clase insegura puede operar la memoria directamente, lo cual es muy inseguro. Por lo tanto, el equipo de desarrollo de JDK ha realizado especialmente esta restricción, sin permitir que los desarrolladores usen la clase insegura en canales regulares, pero usan funciones inseguras en la clase central en Rt.jar.
La pregunta es, ¿qué debemos hacer si realmente queremos instanciar la clase insegura y usar la función insegura?
No debemos olvidar la tecnología negra de la reflexión y usar la reflexión universal para obtener el método de instancia de Insafe. El código es el siguiente:
paquete com.hjc; import sun.misc.unsafe; import java.lang.reflect.field;/*** creado por Cong el 2018/6/6. */public class testUnsafe {inseguro inseguro inseguro estático; static final Long StateOffset; Estado de larga volátil privado = 0; static {try {// reflexionar para obtener la variable de miembro delunsafe TheUnsafe (2.2.10) campo campo = unsafe.class.getDeclaredfield ("TheUnsafe"); // Establecer en el campo Accesable (2.2.11) Field.SetAccessible (True); // Obtener el valor de esta variable (2.2.12) unsafe = (insegurar) campo.get (nulo); // Obtenga el desplazamiento de estado en testUnsafe (2.2.13) stateOffset = unsafe.objectFieldOffset (testUnsafe.class.getDeclaredfield ("estado")); } catch (excepción ex) {System.out.println (ex.getLocalizedMessage ()); tirar un nuevo error (ex); }} public static void main (string [] args) {testUnsafe test = new testUnsafe (); Éxito booleano = unsafe.com PAREANDSWAPINT (test, stateOffset, 0, 1); System.out.println (éxito); }}Si el código anterior (2.2.10 2.2.11 2.2.12) refleja el ejemplo de inseguro, el resultado de ejecución es el siguiente:
2. Investigación sobre el código fuente de la clase Locksupport
Locksupport en Rt.jar en JDK es una clase de herramientas, y su función principal es suspender y despertar hilos. Es la base para crear cerraduras y otras clases de sincronización.
La clase Locksupport se asociará con cada hilo que lo use. El hilo que llama al método de la clase Locksupport por defecto no tiene una licencia. Locksupport se implementa internamente utilizando la clase insegura.
Aquí debemos prestar atención a varias funciones importantes de Locksupport, como sigue:
1.Void Park (): Si el hilo llamado a Park () ha obtenido la licencia asociada con Locksupport, entonces llamar a Locksupport.park () regresará de inmediato. De lo contrario, el hilo de llamadas tendrá prohibido participar en la programación del hilo, es decir, será bloqueado y suspendido. El siguiente código es el siguiente ejemplo:
paquete com.hjc; import java.util.concurrent.locks.locksupport;/*** creado por Cong el 2018/6/6. */public class LocksupportTest {public static void main (string [] args) {System.out.println ("Park Start!"); Locksupport.park (); System.out.println ("Park Stop!"); }}Como se muestra en el código anterior, llame directamente al método del parque en la función principal, y el resultado final solo generará el arranque del parque. Luego, el hilo actual se suspenderá, porque el hilo de llamada no posee una licencia por defecto. Los resultados de la operación son los siguientes:
Cuando ve que otros subprocesos llaman al método nok (hilo de subproceso) y el hilo actual se usa como parámetro, el hilo que llama al método del parque regresará. Además, otros hilos llaman al método de interrupción () del hilo de bloqueo. Cuando se establece el indicador de interrupción o el hilo de bloqueo regresará después del falso despertar del hilo, es mejor usar condiciones de bucle para juzgar.
Cabe señalar que el hilo que llama al método Park () bloqueado se ve interrumpido por otros hilos y los retornos de hilo bloqueados no lanzarán una excepción de InterruptedException.
2. Método de No Unark (hilo de subproceso) Cuando un subproceso llama a Unpark, si el subproceso de parámetro no contiene la licencia asociada con el subproceso y la clase de soporte de bloqueo, deje que el hilo lo mantenga. Si el hilo llamado Park () se suspende antes y se llama el hilo, el hilo se despertará después de que se llame a Invark.
Si el hilo no ha llamado al parque antes, después de llamar al método nok, el método Park () se devolverá de inmediato. El código anterior se modifica de la siguiente manera:
paquete com.hjc; import java.util.concurrent.locks.locksupport;/*** creado por Cong el 2018/6/6. */public class LocksupportTest {public static void main (string [] args) {System.out.println ("Park Start!"); // Haga que el hilo actual obtenga la licencia LockSupport.unpark (Thread.CurrentThread ()); // llame a Park Locksupport.park () nuevamente; System.out.println ("Park Stop!"); }}Los resultados de la operación son los siguientes:
A continuación, estamos viendo un ejemplo para profundizar nuestra comprensión de Park, sin marcar, el código es el siguiente:
import java.util.concurrent.locks.locksupport;/*** creado por Cong el 2018/6/6. */public class LockSupportTest {public static void main (string [] args) lanza interruptedException {Thread Thread = new Thread (new Runnable () {@Override public void run () {System.out.println ("Child Thread Park Start!"); // Llame al método de park y colgando bloqueo a ti mismo bloqueo. }}); // Inicie el hilo infantil Thread.Start (); // El hilo principal duerme 1s hilo. Sleep (1000); System.out.println ("¡Inicio de hilo principal sin marca!"); // Llame a Invark para dejar que el hilo mantenga la licencia, y luego el método del parque devolverá Locksupport.unpark (hilo); }}Los resultados de la operación son los siguientes:
El código anterior primero crea un hilo infantil. Después del inicio, el hilo infantil llama al método del parque. Dado que el hilo infantil predeterminado no posee una licencia, se colgará.
El hilo principal duerme para 1s. ¡El propósito es que el hilo principal llama al método no tardío y permite que el hilo infantil emita el parque de subprocesos infantiles! y bloque.
Luego, el hilo principal ejecuta el método no ACK, siendo el parámetro el hilo infantil, el propósito es dejar que el hilo infantil mantenga la licencia, y luego el método de parque llamado por el hilo infantil regresa.
Al devolver el método del parque, no le dirá qué razón está regresando. Por lo tanto, la persona que llama debe verificar nuevamente si la condición está satisfecha en función de qué método de parque estaba en la llamada actual. Si no se cumple, necesita volver a llamar al método del parque.
Por ejemplo, el estado de interrupción de un hilo cuando regresa, se puede determinar si regresa porque se interrumpe en función de la comparación del estado de interrupción antes y después de la llamada.
Para ilustrar que el hilo después de llamar al método del parque regresará después de que se interrumpe, modifique el código de ejemplo anterior y elimine Locksupport.unpark (hilo); y luego agregue Thread.interrupt (); El código es el siguiente:
import java.util.concurrent.locks.locksupport;/*** creado por Cong el 2018/6/6. */public class LockSupportTest { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Subthread park start!"); // Call the park method and hang yourself. Only interrupts will exit the loop while (! Thread.CurrentThread (). ISinterrupted ()) {LockSupport.Park (); // Inicie Child Thread Thread.Start (); // El hilo principal duerme 1s hilo. Sleep (1000); System.out.println ("¡Inicio de hilo principal sin marca!"); // interrumpir el hilo infantil hilo.interrupt (); }}Los resultados de la operación son los siguientes:
Como el código anterior, el hilo infantil solo terminará después de interrumpir el hilo del niño. Si el hilo infantil no se interrumpe, incluso si llama a Unpark (hilo), el hilo infantil no terminará.
3. Método de parknanos (nanos largos) no: si el parque de llamadas de hilo ha obtenido la licencia asociada con Locksupport, entonces llamar a Locksupport.park () regresará de inmediato. La diferencia es que si el hilo que llama al hilo no se obtiene, se suspenderá y luego regresará después del tiempo de Nanos.
Park también admite tres métodos con parámetros de bloqueador. Cuando un hilo llama a Park sin tener una licencia y está bloqueado y suspendido, el objeto del bloqueador se registrará dentro del hilo.
Use herramientas de diagnóstico para observar la razón por la cual el hilo está bloqueado. Las herramientas de diagnóstico usan el método GetBlocker (Thread) para obtener el objeto Bloqueador. Por lo tanto, JDK recomienda que usemos el método Park con parámetros de bloqueador y establezcamos el bloqueador en esto, por lo que cuando el volcado de memoria soluciona el problema, podemos saber qué clase está bloqueada.
Los ejemplos son los siguientes:
import java.util.concurrent.locks.locksupport;/*** creado por Cong el 2018/6/6. */public class testPark {public void testPark () {Locksupport.park (); // (1)} public static void main (string [] args) {testpark testPark = new testPark (); testpark.testPark (); }}Los resultados de la operación son los siguientes:
Puede ver que está ejecutando el bloqueo, por lo que necesitamos usar las herramientas en el directorio JDK/bin para echar un vistazo. Si no lo sabe, se recomienda echar un vistazo a las herramientas de monitoreo JVM primero.
Cuando se usa Jstack PID para ver la pila de subprocesos después de ejecutar, los resultados que puede ver son los siguientes:
Luego modificamos el código anterior (1) de la siguiente manera:
Locksupport.park (this); // (1)
Ejecutarlo nuevamente y use Jstack PID para ver los resultados de la siguiente manera:
Se puede ver que después del método del parque con el bloqueador, la pila de subprocesos puede proporcionar más información sobre los objetos de bloqueo.
Luego verificaremos el código fuente de la función Park (bloqueador de objetos), el código fuente es el siguiente:
Public static void Park (bloqueador de objetos) {// Obtener el hilo de llamado t = hilo.currentThread (); // Establecer la variable de bloqueador setBlocker (t, bloqueador); // colgar el hilo inseguro.park (falso, 0l); // Borrar la variable del bloqueador después de que se activa el hilo, porque la razón generalmente se analiza cuando el hilo se bloquea setblocker (t, null);}Hay una variable en la clase de subprocesos de objeto volátil Volátil Parkblocker. Se utiliza para almacenar el objeto de bloqueador pasado por el parque, es decir, la variable del bloqueador se almacena en la variable miembro del hilo llamando al método del parque.
4. La función Void Parknanos (bloqueador de objetos, Long Nanos) tiene un tiempo de tiempo de espera adicional en comparación con el parque (bloqueador de objetos).
5. Void Parkuntil (bloqueador de objetos, fecha límite larga) El código fuente de Parkuntil es el siguiente:
public static void Parkuntil (bloqueador de objetos, fecha límite larga) {Thread t = Thread.CurrentThread (); setblocker (t, bloqueador); // isabsolute = true, time = fecha límite; significa que inseguro.Park (verdadero, fecha límite); setblocker (t, nulo); }Puede ver que es una fecha límite establecida, la unidad de tiempo es milisegundos, que se convierte en un valor después de milisegundos desde 1970 hasta el momento actual. La diferencia entre esto y Parknanos (bloqueador de objetos, nanos largos) es que el último calcula el tiempo de nanos de espera desde la época actual, mientras que el primero especifica un punto de tiempo.
Por ejemplo, debemos esperar hasta las 20:34 en 2018.06.06, y luego convertir este punto de tiempo en el número total de milisegundos desde 1970 hasta este punto de tiempo.
Veamos otro ejemplo, el código es el siguiente:
import java.util.queue; import java.util.concurrent.concurrentlinkedqueue; import java.util.concurrent.atomic.atomicboolean; import java.util.concurrent.locks.locksupport;/*** creado por Cong en 2018/6/6. */public class Fifomutex {private final atomicboolean bloqueado = new AtomicBoolean (falso); cola final privada <Shift> Waiters = new concurrentLinkedQueue <Shustar> (); public void Lock () {boolean wasinterrupted = false; Thread Current = Thread.CurrentThread (); camareros.add (actual); // Solo el hilo del jefe del equipo puede obtener el bloqueo (1) while (Waiters.peek ()! = Current || if (thread.interrupted ()) // (2) wasinterrupted = true; } camareros.remove (); if (wasinterrupted) // (3) current.interrupt (); } public void desbloock () {Locked.set (falso); Locksupport.unpark (camareros.peek ()); }}Puede ver que este es un bloqueo en primer lugar, es decir, solo el elemento de encabezado de cola puede obtenerlo. El código (1) si el hilo actual no es el encabezado de la cola o el bloqueo actual ha sido adquirido por otros subprocesos, llame al método del parque para suspenderlo.
Entonces el código (2) hace un juicio. Si el método del parque regresa porque se interrumpe, se ignora la interrupción y el indicador de interrupción se restablece, y solo se realiza una bandera, y luego determina nuevamente si el hilo actual es el elemento de la cabeza de la cola o si el bloqueo ha sido adquirido por otros hilos. Si es así, continúe llamando al método del parque para colgarte.
Entonces, si la marca es verdadera en el código (3), el hilo se interrumpirá. ¿Cómo entiendes esto? De hecho, otros hilos interrumpieron el hilo. Aunque no estoy interesado en la señal de interrupción y la ignora, no significa que otros hilos no estén interesados en la bandera, por lo que necesito restaurarla.
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.