El concepto de bloqueo libre se ha mencionado en la introducción de [Java 1 de alta concurrencia]. Dado que hay una gran cantidad de aplicaciones sin bloqueo en el código fuente de JDK, aquí se introduce Lock-Free.
1 explicación detallada del principio de la clase sin bloqueo
1.1 CAS
El proceso del algoritmo CAS es el siguiente: contiene 3 parámetros CAS (V, E, N). V representa la variable que se actualizará, E representa el valor esperado y N representa el nuevo valor. Solo si v
Cuando el valor es igual al valor E, el valor de V se establecerá en N. Si el valor V es diferente del valor E, significa que otros subprocesos ya han realizado actualizaciones y el hilo actual no hace nada. Finalmente, CAS devuelve el verdadero valor de las operaciones de V. CAS actuales se llevan a cabo con una actitud optimista, y siempre cree que puede completar con éxito las operaciones. Cuando múltiples hilos operan una variable usando CAS al mismo tiempo, solo uno ganará y actualizará con éxito, y el resto fallará. El hilo fallido no se suspenderá, solo se dice que la falla está permitida y se permite que vuelva a intentarlo y, por supuesto, el hilo fallido también permitirá que la operación se abandone. Basado en este principio, CAS
La operación se bloquea inmediatamente y otros roscas también pueden detectar la interferencia al hilo actual y manejarlo adecuadamente.
Encontraremos que hay demasiados pasos en CAS. ¿Es posible que después de juzgar que V y E sean los mismos, cuando estamos a punto de asignar valores, cambiamos el hilo y cambiamos el valor? ¿Qué causó la inconsistencia de los datos?
De hecho, esta preocupación es redundante. Todo el proceso de operación de CAS es una operación atómica, que se completa mediante una instrucción de CPU.
1.2 Instrucciones de CPU
La instrucción de la CPU de CAS es CMMPXCHG
El código de instrucción es el siguiente:
/ * acumulador = al, ax o eax, dependiendo de si se está realizando una comparación de byte, palabra o doble palabra */ if (acumulator == destino) {zf = 1; Destino = fuente; } else {zf = 0; acumulador = destino; } Si el valor de destino es igual al valor en el registro, se establece un indicador de salto y los datos originales se establecen en el destino. Si no espera, no configurará la bandera de salto.
Java proporciona muchas clases sin bloqueo, así que introduzcamos clases sin bloqueo a continuación.
2 inútil
Ya sabemos que el bloqueo sin bloqueo es mucho más eficiente que el bloqueo. Echemos un vistazo a cómo Java implementa estas clases sin bloqueo.
2.1. Atómica
AtomicInteger, como Integer, ambos heredan la clase numérica
clase pública AtomicInteger extiende el número de implementos Java.io.serializable
Hay muchas operaciones CAS en AtomicInteger, típicas de las cuales son:
Public Final Boolean CompareandSet (int espere, int actualización) {
return unsafe.compareanddswapint (this, valueOffset, espere, actualizar);
}
Aquí le explicaremos el método unsafe.com Parearandswapint. Significa que si el valor de la variable cuya compensación en esta clase es valueOffset es el mismo que el valor esperado, entonces establezca el valor de esta variable en actualización.
De hecho, la variable con el valor de compensación es valor
static {try {valueOffset = unsafe.objectFieldOffset (atomicInTEGER.class.getDeclaredField ("valor")); } catch (Exception Ex) {Throw New Error (Ex); }}Hemos dicho antes que CAS puede fallar, pero el costo de la falla es muy pequeño, por lo que la implementación general está en un bucle infinito hasta que tiene éxito.
public Final int getAndincrement () {for (;;) {int current = get (); int next = corriente + 1; if (compareandSet (actual, siguiente)) return corriente; }}2.2 inseguro
Desde el nombre de la clase, podemos ver que las operaciones inseguras son operaciones no seguras, como:
Establezca el valor de acuerdo con el desplazamiento (he visto esta función en el atomicInteger que se acaba de introducir)
Park () (Detente este hilo, se mencionará en el blog futuro)
La API no pública de operación CAS subyacente puede diferir enormemente en diferentes versiones de JDK.
2.3. Atomicreferencia
AtomicInteger se ha mencionado anteriormente, y por supuesto, atómico, atómico, etc., son todos similares.
Lo que queremos introducir aquí es Atomicreference.
Atomicreference es una clase de plantilla
clase pública Atomicreference <v> implementa java.io.serializable
Se puede usar para encapsular cualquier tipo de datos.
Por ejemplo, cadena
prueba de paquete; import java.util.concurrent.atomic.atomicreference; prueba de clase pública {public final Static Atomicreference <String> Atomicstring = new Atomicreference <String> ("HOSEE"); public static void main (string [] args) {for (int i = 0; i <10; i ++) {final int num = i; new Thread () {public void run () {try {Thread.sleep (Math.abs ((int) Math.random ()*100)); } catch (Exception e) {E.PrintStackTrace (); } if (atomicstring.compareAndSet ("HOSEE", "ZTK")) {System.out.println (Thread.CurrentThread (). GetId () + "Valor de cambio"); } else {System.out.println (Thread.CurrentThread (). getId () + "fallido"); }}; }.comenzar(); }}}resultado:
10 fallado
13 Cailed
Valor de cambio de 9 de cambio
11 Cailed
12 Cailed
15 Cailed
17 Cailed
14 Cailed
16 Cailed
18 Cailed
Puede ver que solo un hilo puede modificar el valor, y los subprocesos no pueden modificarlo más.
2.4.AtomicStampedReference
Encontraremos que todavía hay un problema con la operación CAS.
Por ejemplo, el método de incrementandget de AtomicInseger anterior
public final int incremementandget () {for (;;) {int current = get (); int next = corriente + 1; if (compareandSet (actual, siguiente)) return Next; }} Suponga que el valor actual = 1 Cuando un subproceso int centre = get () se ejecuta, cambie a otro hilo, este hilo convierte 1 en 2, y luego otro hilo convierte 2 en 1 nuevamente. En este momento, cambie al hilo inicial. Dado que el valor todavía es igual a 1, las operaciones CAS aún se pueden realizar. Por supuesto, no hay problema con la suma. Si hay algunos casos, este proceso no se permitirá cuando sea sensible al estado de los datos.
En este momento, se requiere la clase Atomicstampedreference.
Implementa una clase de par internamente para encapsular valores y marcas de tiempo.
par de clase estática privada <T> {referencia t final; Sello de int Final Int; par de pares privados (t reference, int sello) {this.reference = reference; this.stamp = sello; } static <t> par <t> de (t reference, int sello) {return new Par <t> (referencia, sello); }}La idea principal de esta clase es agregar marcas de tiempo para identificar cada cambio.
// Compare los parámetros de configuración son: el valor esperado escribe el nuevo valor espere la marca de tiempo nueva marca de tiempo
Public Boolean CompareandSet (V esperadoReference, v NewReference, int esperado, int NewStamp) {par <v> current = par; return esperadoReference == current.reference && esperado == current.stamp && ((newreference == current.reference && newstamp == current.stamp) || caspair (current, par.of (newreference, newStamp))); } Cuando el valor esperado es igual al valor actual y la marca de tiempo esperada es igual a la marca de tiempo actual, se escribe el nuevo valor y se actualiza la nueva marca de tiempo.
Aquí hay un escenario que usa AtomicStampedReference. Puede que no sea adecuado, pero no puedo imaginar un buen escenario.
El fondo de la escena es que una empresa recarga a los usuarios con bajo equilibrio de forma gratuita, pero cada usuario solo puede recargarse una vez.
prueba de paquete; import java.util.concurrent.atomic.atomicstampedreference; prueba de clase pública {static atomicstampedreference <integer> dinero = new AtomicStampedReference <integer> (19, 0); public static void main (string [] args) {for (int i = 0; i <3; i ++) {final int timestamp = money.getStamp (); new Thread () {public void run () {while (true) {while (true) {integer m = money.getReference (); if (m <20) {if (Money.CompareAndSet (M + 20, Timestamp, Timestamp + 1)) {System.out.println ("Recarga con éxito, equilibrio:" + Money.getReference ()); romper; }} else {break; }}}}}}; }.comenzar(); } new Thread () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int timestamp = money.getStamp (); Entero m = dinero.getReference (); if (m> 10) {if (Money.CompareAndSet (M, M - 10, Timestamp, Timestamp + 1)) {System.out.println ("Consumido 10 yuan, Balance:" + Money.getReference ()); romper; }} else {break; }} try {thread.sleep (100); } catch (Exception e) {// tODO: manejar excepción}}}}; }.comenzar(); }}Explique el código, hay 3 hilos recargan al usuario. Cuando el equilibrio del usuario es inferior a 20, recarga el usuario 20 yuanes. Se consumen 100 hilos, cada uno gastando 10 yuanes. El usuario inicialmente tiene 9 yuanes. Al usar AtomicStampedReference para implementarlo, el usuario solo se recargará una vez, porque cada operación causa la marca de tiempo +1. Resultados de ejecución:
Recarga con éxito, equilibrio: 39
Consumo de 10 yuanes, equilibrio: 29
Consumo de 10 yuanes, equilibrio: 19
Consumo de 10 yuanes, equilibrio: 9
Si usa AtomicReference <integer> o Atomic Integer para implementarlo, causará múltiples recargas.
Recarga con éxito, equilibrio: 39
Consumo de 10 yuanes, equilibrio: 29
Consumo de 10 yuanes, equilibrio: 19
Recarga con éxito, equilibrio: 39
Consumo de 10 yuanes, equilibrio: 29
Consumo de 10 yuanes, equilibrio: 19
Recarga con éxito, equilibrio: 39
Consumo de 10 yuanes, equilibrio: 29
2.5. AtomicIntegerarray
En comparación con AtomicInteger, la implementación de matrices es solo un subíndice adicional.
Public Final Boolean CompareandSet (int i, int espere, int actualización) {
devolver compareSetraw (checkedByTeOffset (i), esperar, actualizar);
}
Su interior simplemente encapsula una matriz normal
matriz privada final int [];
Lo interesante aquí es que los ceros principales de los números binarios se utilizan para calcular el desplazamiento en la matriz.
shift = 31 - integer.numberOfLeadingzeros (escala);
El cero principal significa que, por ejemplo, 8 bits representan 12,00001100, entonces el cero principal es el número de 0 frente a 1, que es 4.
Cómo calcular el desplazamiento no se introduce aquí.
2.6. AtomicIntegerfieldupdater
La función principal de la clase AtomicIntegieldupdater es permitir que las variables ordinarias también disfruten de las operaciones atómicas.
Por ejemplo, originalmente había una variable que era tipo int, y esta variable se aplicó en muchos lugares. Sin embargo, en un determinado escenario, si desea convertir el tipo INT en AtomicInteger, si cambia el tipo directamente, debe cambiar la aplicación en otros lugares. AtomicIntegerFieldupdater está diseñado para resolver tales problemas.
prueba de paquete; import java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicintegerfieldupdater; prueba de clase pública {public static class v {int id; Puntuación volátil int; public int getScore () {return score; } public void setScore (int store) {this.score = stork; }} Public final estática AtomicInTegerFieldUpdater <v> vv = AtomicInTegerFieldUpdater.newupdater (v.class, "Score"); public static atomicInteger allScore = new AtomicInteger (0); public static void main (string [] args) lanza interruptedException {final v stu = new v (); Hilo [] t = nuevo hilo [10000]; for (int i = 0; i <10000; i ++) {t [i] = new Thread () {@Override public void run () {if (math.random ()> 0.4) {vv.incrementandget (stu); allScore.Incrementandget (); }}}; t [i] .Start (); } para (int i = 0; i <10000; i ++) {t [i] .Join (); } System.out.println ("score ="+stu.getscore ()); System.out.println ("allScore ="+AllScore); }} El código anterior convierte la puntuación utilizando AtomicInTEGERFIELDUPDATER en AtomicInteger. Asegurar la seguridad del hilo.
AllScore se usa aquí para verificar. Si la puntuación y los valores de AllScore son los mismos, significa que es seguro de hilo.
Nota: