¿Qué es un bloqueo de spinning?
Hablando de cerraduras de spin, debemos comenzar con el mecanismo de bloqueo bajo múltiples subprocesos. Dado que algunos recursos en un entorno del sistema múltiple son limitados, a veces requieren exclusión mutua. En este momento, se introducirá un mecanismo de bloqueo. Solo el proceso que adquiere el bloqueo puede obtener acceso a recursos. Es decir, solo un proceso puede adquirir el bloqueo a la vez para ingresar a su propia área crítica. Al mismo tiempo, dos o más procesos no pueden ingresar al área crítica. Al salir del área crítica, se lanzará la cerradura.
Al diseñar un algoritmo mutex, siempre enfrenta una situación en la que no tiene un bloqueo, es decir, ¿qué debe hacer si no obtiene una cerradura?
Por lo general, hay 2 formas de lidiar con eso:
Una es que la persona que llama que no ha obtenido la cerradura está enrollando por allí para ver si el soporte del bloqueo de giro ha liberado la cerradura. Este es el foco de este artículo: bloqueo de spin. No tiene que bloquear la Ciudad de la línea (sin bloqueo).
Otra forma es que el proceso sin obtener los bloqueos de bloqueo en sí mismo (bloqueo) y continúa ejecutando otras tareas en el hilo, que es el mutex (incluido el bloqueo incorporado sincronizado, reentrantlock, etc.).
introducción
CAS (Compare y Swap), es decir, comparación e intercambio, también es la operación central que implementa lo que generalmente llamamos bloqueo de spinning o bloqueo optimista.
Su implementación es muy simple, lo que es comparar un valor esperado con un valor de memoria. Si los dos valores son iguales, reemplace el valor de memoria con el valor esperado y devuelva verdadero. De lo contrario, devuelva falso.
Asegurar la operación atómica
Cualquier tecnología emerge para resolver ciertos problemas específicos. El problema que CAS debe resolver es garantizar las operaciones atómicas. ¿Qué es una operación atómica? Los átomos son los más pequeños e indecentes, y la operación atómica es la operación más pequeña e indecente. Es decir, una vez que se inicia la operación, no se puede interrumpir y sabe que la operación se completa. En un entorno múltiple, las operaciones atómicas son un medio importante para garantizar la seguridad de los hilos. Por ejemplo, suponga que hay dos hilos funcionando y desean modificar un cierto valor. Tome la operación de autoincremento como ejemplo. Para realizar la operación de autoincremento en un entero I, se requieren tres pasos básicos:
1. Lea el valor actual de I;
2. Agregue 1 al valor I;
3. Escribe el valor I de nuevo a la memoria;
Supongamos que ambos procesos leen el valor actual de I, suponiendo que es 0, en este momento, el subproceso A agrega 1 a I, el subproceso B también agrega 1, y finalmente I es 1, no 2. Esto se debe a que la operación de autoincremento no es una operación atómica, y los tres pasos divididos se pueden interferir. Como en el ejemplo a continuación, para 10 subprocesos, cada hilo realiza 10,000 operaciones I ++, el valor esperado es de 100,000, pero desafortunadamente, el resultado siempre es inferior a 100,000.
static int i = 0; public static void add () {i ++; } clase estática privada Plus implementa runnable {@Override public void run () {for (int k = 0; k <10000; k ++) {add (); }}} public static void main (string [] args) lanza interruptedException {hilo [] hilos = nuevo hilo [10]; para (int i = 0; i <10; i ++) {hilos [i] = new Thread (new Plus ()); hilos [i] .Start (); } para (int i = 0; i <10; i ++) {hilos [i] .Join (); } System.out.println (i); }En este caso, ¿qué debo hacer? Así es, tal vez ya lo haya pensado, puede bloquear o usar la implementación sincronizada, por ejemplo, modificar el método add () al siguiente:
public sincronizado void static add () {i ++; }Alternativamente, la operación de bloqueo se implementa, por ejemplo, utilizando ReentrantLock (ReentrantLock).
bloqueo de bloqueo estático privado = nuevo reentrantlock (); public static void add () {Lock.lock (); i ++; Lock.unlock (); } CAS implementa bloqueo de spin
Dado que las operaciones atómicas se pueden implementar utilizando el bloqueo o la palabra clave sincronizada, ¿por qué usar CAS? Debido a que el bloqueo o el uso de palabras clave sincronizadas trae una gran pérdida de rendimiento, mientras que el uso de CAS puede lograr un bloqueo optimista. En realidad, utiliza directamente las instrucciones de nivel de CPU, por lo que el rendimiento es muy alto.
Como se mencionó anteriormente, CAS es la base para implementar bloqueos de spinning. CAS utiliza instrucciones de CPU para garantizar la atomicidad de la operación para lograr el efecto de bloqueo. En cuanto al giro, también es muy claro leer el significado literal. Si lo gira usted mismo, es un bucle. Generalmente se implementa utilizando un bucle infinito. De esta manera, una operación CAS se ejecuta en un bucle infinito. Cuando la operación es exitosa y devuelve verdadera, el bucle termina; Cuando se falsa, el bucle se ejecuta y la operación CAS se continúa hasta que se devuelve la verdadera.
De hecho, muchos lugares en el JDK usan CAS, especialmente en el paquete java.util.concurrent, como CountdownLatch, Semaphore, Reentrantlock y Java.util.concurrent.atomic Package. Creo que todos han usado Atomic*, como Atomicboolean, AtomicInteger, etc.
Aquí tomamos el atomicboolean como ejemplo, porque es bastante simple.
Clase pública Atomicboolean implementos java.io.serializables {private static final long serialversionUid = 4654671469794556979l; // Configurar para usar unsafe.com Parpeandswapint para actualizaciones privadas estáticas finales inseguros inseguer = unsafe.getUnsafe (); Private Static final Long ValueOffet; static {try {valueOffset = unsafe.objectFieldOffset (atomicboolean.class.getDeclaredField ("valor")); } catch (Exception Ex) {Throw New Error (Ex); }} Valor privado Volátil Volátil; Public final boolean get () {Valor de retorno! = 0; } Public Final Boolean CompareandSet (boolean esperanza, actualización booleana) {int e = esperar? 1: 0; int u = actualizar? 1: 0; return unsafe.compareanddswapint (this, valueOffset, e, u); }}Esto es parte del Código de Atomicboolean, y vemos varios métodos y propiedades clave aquí.
1. Se usa el objeto Sun.Misc.unsafe. Esta clase proporciona una serie de métodos para operar directamente los objetos de memoria, pero JDK solo usa internamente, y no se recomienda que los desarrolladores los usen;
2. El valor representa el valor real. Puede ver que el método GET realmente juzga el valor booleano en función de si el valor es igual a 0. El valor aquí se define como volátil, porque volátil puede garantizar la visibilidad de la memoria, es decir, siempre que el valor cambie, otros subprocesos pueden ver el valor cambiado de inmediato. El próximo artículo hablará sobre la visibilidad de Volátil, bienvenido a seguir
3. ValueOffset es el desplazamiento de memoria del valor de valor, obtenido utilizando el método Unsafe.ObjectFieldOffset y utilizado como el método de Compara de Comparación posterior;
4. Método de comparación, este es el método central para implementar CAS. Al usar el método Atomicboolean, solo necesita pasar el valor esperado y el valor a actualizar. Se llama al método unsafe.compareandswapint (this, valueOffset, e, u). Es un método nativo, implementado en C ++, y el código específico no se publicará. En resumen, utiliza la instrucción CMMPXCHG de la CPU para completar la comparación y el reemplazo. Por supuesto, dependiendo de la versión específica del sistema, también hay diferencias en la implementación. Los que están interesados pueden buscar los artículos relevantes por sí mismos.
Use escenarios
Por ejemplo, Atomicboolean se puede usar en tal escenario. El sistema debe determinar si algunas operaciones de inicialización deben realizarse en función de las propiedades estatales de una variable booleana. Si se trata de un entorno múltiple y evite las ejecuciones repetidas, se puede implementar utilizando AtomicBoolean. El pseudocódigo es el siguiente:
Bandera de atomicboolean estática final privada = new AtomicBoolean (); if (flag.CompareAndSet (false, true)) {init (); }Por ejemplo, AtomicInteger se puede usar en contadores y en entornos de múltiples subprocesos para garantizar un conteo preciso.
Preguntas de ABA
Hay un problema con CAS, que es que un valor cambia de A a B y luego de B a A. En este caso, CAS pensará que el valor no ha cambiado, pero de hecho ha cambiado. En este sentido, hay AtomicStampedReference en paquetes concurrentes que proporcionan una implementación basada en el número de versión, que puede resolver algunos problemas.
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.