En desarrollo general, el autor a menudo ve que muchos estudiantes solo usan algunos métodos básicos para tratar el modelo de desarrollo concurrente de Java. Por ejemplo, volátil, sincronizado. Los paquetes concurrentes avanzados como el bloqueo y el atómico a menudo no son utilizados por muchas personas. Creo que la mayoría de las razones se deben a la falta de atributos al principio. En el trabajo de desarrollo ocupado, ¿quién puede comprender y usar con precisión el modelo de concurrencia correcto?
Así que recientemente, según esta idea, planeo organizar el mecanismo de control de concurrencia en un artículo. No es solo un recuerdo de su propio conocimiento, sino que también espera que el contenido mencionado en este artículo pueda ayudar a la mayoría de los desarrolladores.
El desarrollo paralelo del programa inevitablemente involucra problemas como la colaboración multitrepero y la colaboración de tareas múltiples y el intercambio de datos. En JDK, se proporcionan múltiples formas para implementar un control concurrente entre múltiples hilos. Por ejemplo, comúnmente utilizado: bloqueo interno, bloqueo de reingreso, bloqueo de lectura y semáforo.
Modelo de memoria Java
En Java, cada hilo tiene un área de memoria de trabajo, que almacena una copia del valor de la variable en la memoria principal compartida por todos los subprocesos. Cuando un hilo se ejecuta, opera estas variables en su propia memoria de trabajo.
Para acceder a una variable compartida, un hilo generalmente adquiere el bloqueo y borra su área de memoria de trabajo, lo que garantiza que la variable compartida se cargue correctamente desde el área de memoria compartida de todos los hilos en el área de memoria de trabajo del hilo. Cuando el hilo se desbloquea, se garantiza que el valor de la variable en el área de memoria de trabajo se asociará con la memoria compartida.
Cuando un hilo usa una cierta variable, independientemente de si el programa usa las operaciones de sincronización de subprocesos correctamente, el valor que obtiene debe ser el valor almacenado en la variable por sí mismo u otros subprocesos. Por ejemplo, si dos hilos almacenan diferentes valores u referencias de objetos en la misma variable compartida, entonces el valor de la variable es de este hilo o de ese hilo, y el valor de la variable compartida no se compusirá de los valores de referencia de los dos subprocesos.
Una dirección a la que los programas Java pueden acceder cuando se usa una variable. No solo incluye variables de tipo básico y variables de tipo de referencia, sino también variables de tipo de matriz. Las variables almacenadas en el área de memoria principal pueden ser compartidas por todos los hilos, pero es imposible que un hilo acceda a los parámetros o variables locales de otro hilo, por lo que los desarrolladores no tienen que preocuparse por los problemas de seguridad de los hilos de las variables locales.
Se pueden ver variables volátiles entre múltiples hilos
Dado que cada hilo tiene su propio área de memoria de trabajo, puede ser invisible para otros subprocesos cuando un hilo cambia sus propios datos de memoria de trabajo. Para hacer esto, puede usar la palabra clave volátil para romper todos los hilos para leer y escribir variables en la memoria, de modo que las variables volátiles sean visibles entre múltiples hilos.
Las variables declaradas como volátiles se pueden garantizar de la siguiente manera:
1. Las modificaciones de las variables por otros hilos se pueden reflejar rápidamente en el hilo actual;
2. Asegúrese de que la modificación del hilo actual de la variable volátil se pueda volver a escribir a la memoria compartida en el tiempo y ver por otros hilos;
3. Use variables declaradas por volátiles, y el compilador garantizará su orden.
Palabras clave sincronizadas
La palabra clave sincronizada sincronizada es uno de los métodos de sincronización más utilizados en el lenguaje Java. En las primeras versiones de JDK, el rendimiento de Synchronized no fue muy bueno, y el valor era adecuado para ocasiones en que la competencia de bloqueo no era particularmente feroz. En JDK6, la brecha entre las cerraduras sincronizadas e injustas se ha reducido. Más importante aún, Sincronized es más conciso y claro, y el código es legible y mantenido.
Métodos para bloquear un objeto:
Método void sincronizado público () {}
Cuando se llama al método del método (), el hilo de llamadas primero debe obtener el objeto actual. Si el bloqueo del objeto actual está sostenido por otros hilos, el hilo de llamadas esperará. Después de que termine la violación, se lanzará el bloqueo del objeto. El método anterior es equivalente al siguiente método de escritura:
Public void Method () {sincronizado (this) {// haz algo ...}} En segundo lugar, sincronizado también se puede usar para construir bloques de sincronización. En comparación con los métodos de sincronización, los bloques de sincronización pueden controlar el rango del código de sincronización con mayor precisión. Un pequeño código de sincronización es muy rápido dentro y fuera de las cerraduras, lo que le da al sistema un mayor rendimiento.
Método público vacío (objeto o) {// befefefefronized (o) {// hacer algo ...} // después} Sincronizado también se puede usar para funciones estáticas:
Método vacío estático sincronizado público () {}
Es importante tener en cuenta en este lugar que el bloqueo sincronizado se agrega al objeto de clase actual, por lo que todas las llamadas a este método deben obtener el bloqueo del objeto de clase.
Aunque sincronizado puede garantizar la seguridad de los subprocesos de objetos o segmentos de código, usar sincronizado solo todavía no es suficiente para controlar las interacciones de subprocesos con lógica compleja. Para lograr la interacción entre múltiples hilos, también se requieren los métodos Wait () y notificar () del objeto Objeto.
Uso típico:
sincronizado (obj) {while (<?>) {obj.wait (); // Continuar ejecutando después de recibir la notificación. }} Antes de usar el método Wait (), debe obtener el bloqueo de objeto. Cuando se ejecuta el método Wait (), el hilo actual puede liberar el bloqueo exclusivo de OBJ para usar por otros hilos.
Cuando espera que el hilo en OBJ reciba obj.notify (), puede recuperar el bloqueo exclusivo de OBJ y continuar ejecutándose. Tenga en cuenta que el método notify () es evocar aleatoriamente un hilo que espera en el objeto actual.
Aquí hay una implementación de una cola de bloqueo:
public class Blockqueue {Lista de la lista privada = new ArrayList (); Public sincronizado Object Pop () lanza interruptedException {while (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.remove (0); } else {return null; }} objeto sincronizado público put (object obj) {list.add (obj); this.notify (); }} Sincronizado y Wait () y notificar () debería ser una habilidad básica que los desarrolladores de Java deben dominar.
Reentrantlock Reentrantlock Lock
Reentrantlock se llama Reentrantlock. Tiene características más potentes que sincronizadas, puede interrumpir y tiempo. En el caso de alta concurrencia, tiene ventajas de rendimiento obvias sobre sincronizado.
Reentrantlock proporciona cerraduras justas e injustas. Un bloqueo justo es la primera en primera salida del bloqueo, y no se puede cortar un bloqueo justo. Por supuesto, desde una perspectiva de rendimiento, el rendimiento de las cerraduras injustas es mucho mejor. Por lo tanto, en ausencia de necesidades especiales, se deben preferir cerraduras injustas, pero sincronizado proporciona que la industria de bloqueo no sea absolutamente justa. Reentrantlock puede especificar si el bloqueo es justo al construir.
Cuando use un bloqueo de reingreso, asegúrese de liberar el bloqueo al final del programa. En general, el código para liberar el bloqueo debe escribirse finalmente. De lo contrario, si se produce la excepción del programa, LOACK nunca se lanzará. El bloqueo sincronizado es lanzado automáticamente por el JVM al final.
El uso clásico es el siguiente:
Pruebe {if (Lock.trylock (5, TimeUnit.seconds)) {// Si se ha bloqueado, intente esperar 5s para ver si se puede obtener el bloqueo. Si el bloqueo no se puede obtener después de 5s, devuelva falso para continuar la ejecución // Lock.LockInterruption (); puede responder al evento de interrupción, intente {// operación} finalmente {Lock.unlock (); }}} Catch (InterruptedException e) {E.PrintStackTrace (); // Cuando se interrumpe el hilo actual (interrumpir), se arrojará una Excepción InterruptedEnt}}Reentrantlock proporciona una rica variedad de funciones de control de bloqueo, y aplica de manera flexible estos métodos de control para mejorar el rendimiento de la aplicación. Sin embargo, no se recomienda usar Reentrantlock aquí. Reentry Lock es una herramienta de desarrollo avanzada proporcionada en JDK.
ReadWriteLock Leer y escribir bloqueo
La separación de lectura y escritura es una idea de procesamiento de datos muy común. Debe considerarse una tecnología necesaria en SQL. ReadWriteLock es un bloqueo de separación de lectura-escritura proporcionado en JDK5. Los bloqueos de separación de lectura y escritura pueden ayudar efectivamente a reducir la competencia de bloqueos para mejorar el rendimiento del sistema. Los escenarios de uso para la separación de lectura y escritura son principalmente si en el sistema, el número de operaciones de lectura es mucho mayor que las operaciones de escritura. Cómo usarlo es el siguiente:
Private ReentRantReadWriteLock ReadWriteLock = new ReEntRantReadWriteLock (); private Lock Readlock = ReadWriteLock.ReadLock (); Private Lock WriteLock = ReadWriteLock.WriteLock (); public Panject Handleread () tiros InterruptedException {try readlock.lock (); Thread.sleep (1000); valor de retorno; } finalmente {readlock.unlock (); }} public Object Handleread () lanza interruptedException {try {writelock.lock (); Thread.sleep (1000); valor de retorno; } Finalmente {writeLock.unlock (); }} Objeto de condición
El objeto ConditionD se usa para coordinar la colaboración compleja entre múltiples hilos. Principalmente asociado con cerraduras. Se puede generar una instancia de condición unida al bloqueo a través del método Newcondition () en la interfaz de bloqueo. La relación entre un objeto de condición y un bloqueo es como usar las dos funciones Object.Wait (), Object.Notify () y las palabras clave sincronizadas.
Aquí puede extraer el código fuente de ArrayBlockingqueue:
Class Public ClassBlockingqueue extiende AbstractQueue implementos Bloquingqueue, java.io.Serializable {/** Bloqueo principal Atendiendo todo el acceso*/Bloqueo de Reentrantlock final;/** Condición para esperar*/Condición final privada Notempty;/** Condición para esperar*/Condición final privada NotfulL; IlegalargumentException (); this.items = nuevo objeto [capacidad]; bloqueo = nuevo reentrantlock (justo); noTempty = Lock.NewCondition (); // Generar condición notfull = lock.newCondition ();} public void put (e e) lanza interruptedException {checkNotNull (e); LOQUERA DE REENTRANTLOCHLACT FINAL = this.lock; LOCK.LockInterruption (); intente {while (count == items.length) notfull.await (); insertar (e); } Finalmente {Lock.unlock (); }} private void insert (e x) {elementos [putIndex] = x; putIndex = inc (putIndex); ++ recuento; Notempty.signal (); // notificación} public e Take () lanza interruptedException {final de reentrantLock Lock = this.lock; LOCK.LockInterruption (); intente {while (count == 0) // si la cola está vacía notempty.await (); // Entonces la cola de consumo tiene que esperar un extracto de retorno de señal no vacío (); } Finalmente {Lock.unlock (); }} private e extracto () {objeto final [] elementos = this.items; E x = this. <E> Cast (elementos [TakeIndex]); Elementos [TakeIndex] = NULL; TakeIndex = Inc (TakeIndex); --contar; notflido.signal (); // notificar put () que la cola de hilo tiene retorno de espacio libre x;} // otro código} Semaphore Semaphore <Br /> Semaphore proporciona un método de control más poderoso para la colaboración de múltiples hilos. Semaphore es una extensión de la cerradura. Ya sea el bloqueo interno sincronizado o el ReentrantLock, un hilo permite el acceso a un recurso a la vez, mientras que el semáforo puede especificar que múltiples subprocesos acceden a un recurso al mismo tiempo. Del constructor, podemos ver:
Public Semaphore (Int Permits) {}
Public Semaphore (int Permits, boolean fair) {} // puede especificar si es justo
Los permisos especifican el libro de acceso para el semáforo, lo que significa cuántas licencias se pueden aplicar al mismo tiempo. Cuando cada hilo solo se aplica para una licencia a la vez, esto es equivalente a especificar cuántos hilos pueden acceder a un cierto recurso al mismo tiempo. Estos son los principales métodos para usar:
public void adquirir () lanza interruptedException {} // Intenta obtener un permiso de acceso. Si no está disponible, el hilo esperará, sabiendo que un hilo libera un permiso o se interrumpe el hilo actual.
public void adquireUnInterruption () {} // Similar a adquirir (), pero no responde a las interrupciones.
Public boolean tryacquire () {} // intenta obtenerlo, verdadero si tiene éxito, de lo contrario falso. Este método no esperará y regresará de inmediato.
Public boolean tryacquire (tiempo de espera largo, unidad de tiempo de tiempo) lanza InterruptedException {} // ¿Cuánto tiempo se tarda en esperar
Public Void Release () // se utiliza para lanzar una licencia después de que se completa el recurso de acceso en el sitio para que otros hilos que esperan permiso pueden acceder al recurso.
Echemos un vistazo a los ejemplos de uso de semáforos proporcionados en el documento JDK. Este ejemplo explica cómo controlar el acceso a los recursos a través de semáforos.
Public Class Pool {private static final int max_available = 100; privado final de semáforo disponible = nuevo semáforo (max_available, true); public object getItem () lanza interruptedException {disponible.acquire (); // Solicite una licencia // Solo 100 subprocesos pueden ingresar para obtener los artículos disponibles al mismo tiempo, // Si es más de 100, debe esperar a Return GetNexTAVailableM ();} public void putitem (objeto x) {// volver a colocar el elemento dado en el grupo y marcarlo como no usado si (markasunused (x)) {disponible.release (); // Se agregó un elemento disponible, la liberación de una licencia y el hilo que solicita el recurso se activa}} // por ejemplo solo referencia, objeto no protegido de datos [] elementos = nuevo objeto [max_available]; // utilizado para objetos de multiplexación de la piscina de objetos protegidos boolean [] usado = new Boolean [max_available]; // objeto sincronizado protegido de marcado getNextavailableItem () {for (int i = 0; i <max_available; ++ i) {if (! Usado [i]) {usado [i] = true; devolver elementos [i]; }} return null;} protegido Boolean MarkAsunused (elemento de objeto) {for (int i = 0; i <max_available; ++ i) {if (item == elementos [i]) {if (usado [i]) {usado [i] = falso; devolver verdadero; } else {return false; }}} return false;}} Esta instancia simplemente implementa un grupo de objetos con una capacidad máxima de 100. Por lo tanto, cuando hay 100 solicitudes de objetos al mismo tiempo, el grupo de objetos tendrá escasez de recursos y los hilos que no pueden obtener recursos deben esperar. Cuando un hilo termina usando un objeto, debe devolver el objeto al grupo de objetos. En este momento, dado que los recursos disponibles aumentan, se puede activar un hilo que espera el recurso.
ThreadLocal Thread Variables locales <Br /> Después de comenzar a contactar ThreadLocal, es difícil para mí comprender los escenarios de uso de esta variable local de hilo. Al mirar hacia atrás ahora, ThreadLocal es una solución para el acceso concurrente a variables entre múltiples hilos. A diferencia de los métodos de bloqueo sincronizados y otros, ThreadLocal no proporciona bloqueos en absoluto, pero utiliza el método de intercambiar espacio para el tiempo para proporcionar a cada hilo copias independientes de variables para garantizar la seguridad de los subprocesos. Por lo tanto, no es una solución para el intercambio de datos.
Threadlocal es una buena idea para resolver problemas de seguridad de hilos. Hay un mapa en la clase Threadlocal que almacena una copia de variables para cada hilo. La clave del elemento en el mapa es un objeto de subproceso, y el valor corresponde a la copia de las variables para el hilo. Dado que el valor clave no se puede repetir, cada "objeto de hilo" corresponde a la "copia de las variables" del hilo, y alcanza la seguridad del subproceso.
Es particularmente notable. En términos de rendimiento, ThreadLocal no tiene un rendimiento absoluto. Cuando el volumen de concurrencia no es muy alto, el rendimiento del bloqueo será mejor. Sin embargo, como un conjunto de soluciones seguras de hilo que están completamente no relacionadas con las cerraduras, usar ThreadLocal puede reducir la competencia de bloqueo en cierta medida en alta concurrencia o competencia feroz.
Aquí hay un uso simple de ThreadLocal:
Método InitialValue () de la clase pública TestNum {// Sobrescribir el método InitialValue () a través de la clase interna anónima, especifique el valor inicial static shifhlocal seqnum = new ThreadLocal () {public Integer InitialValue () {return 0; }}; // Obtenga el siguiente valor de secuencia public int getNextNum () {seqnum.set (seqnum.get () + 1); return seqnum.get ();} public static void main (string [] args) {testNum sn = new testNum (); // 3 hilos comparten Sn, cada uno generando un número de secuencia testClient t1 = nuevo testClient (sn); TestClient t2 = new testClient (sn); TestClient T3 = New TestClient (Sn); t1.start (); t2.start (); t3.start (); } clase privada de clase estática TestClient extiende el hilo {Private testNum Sn; public testClient (testNum sn) {this.sn = sn; } public void run () {for (int i = 0; i <3; i ++) {// Cada hilo produce 3 valores de secuencia System.out.println ("Thread [" + Thread.CurrentThread (). GetName () + "] -> Sn [" + Sn.getNextNum () + "]"); }}}} Resultado de salida:
Thread [Thread-0]> Sn [1]
Thread [Thread-1]> Sn [1]
Thread [Thread-2]> Sn [1]
Thread [Thread-1]> Sn [2]
Thread [Thread-0]> Sn [2]
Thread [Thread-1]> Sn [3]
Thread [Thread-2]> Sn [2]
Thread [Thread-0]> Sn [3]
Thread [Thread-2]> Sn [3]
La información del resultado de salida se puede encontrar que aunque los números de secuencia generados por cada subproceso comparten la misma instancia de testNum, no interfieren entre sí, pero cada uno genera números de secuencia independientes. Esto se debe a que ThreadLocal proporciona una copia separada para cada hilo.
El rendimiento del bloqueo y la optimización de "cerraduras" son uno de los métodos de sincronización más utilizados. En el desarrollo normal, a menudo puede ver a muchos estudiantes agregando directamente un gran código a la cerradura. Algunos estudiantes solo pueden usar un método de bloqueo para resolver todos los problemas para compartir. Obviamente, tal codificación es inaceptable. Especialmente en entornos de alta concurrencia, la competencia feroz de bloqueo conducirá a una degradación del programa más obvia del programa. Por lo tanto, el uso racional de bloqueos está directamente relacionado con el rendimiento del programa.
1. Subcontratación de subprocesos <Br /> En el caso de múltiples núcleos, el uso de múltiples subprocesos puede mejorar significativamente el rendimiento del sistema. Sin embargo, en situaciones reales, el uso de múltiples subprocesos agregará una sobrecarga adicional del sistema. Además del consumo de recursos de tareas del sistema de un solo núcleo, las aplicaciones de múltiples subprocesos también deben mantener información única adicional de múltiples subprocesos. Por ejemplo, los metadatos del hilo en sí, la programación del hilo, la conmutación de contexto de hilo, etc.
2. Reduzca el tiempo de retención de bloqueo
En los programas que usan bloqueos para el control concurrente, cuando los bloqueos compiten, el tiempo de retención de bloqueo de un solo hilo tiene una relación directa con el rendimiento del sistema. Si el hilo mantiene el bloqueo durante mucho tiempo, la competencia por la cerradura será más intensa. Por lo tanto, durante el proceso de desarrollo del programa, el tiempo para ocupar un cierto bloqueo debe minimizarse para reducir la posibilidad de exclusión mutua entre hilos. Por ejemplo, el siguiente código:
Public sincronizado sincmehod () {beforemethod (); mutexmethod (); afterMethod ();} Si solo el método mutexmethod () en este caso es sincrónico, pero en beforemethod () y Aftermethod () no requieren control de sincronización. Si Beforemethod () y AfterMethod () son métodos de peso pesado, tomará mucho tiempo para la CPU. En este momento, si la concurrencia es grande, usar este esquema de sincronización conducirá a un gran aumento en los hilos de espera. Porque el hilo de ejecución actualmente liberará el bloqueo solo después de que se hayan ejecutado todas las tareas.
La siguiente es una solución optimizada, que solo se sincroniza cuando sea necesario, de modo que el tiempo para que los hilos mantengan los bloqueos se puedan reducir significativamente y se puede mejorar el rendimiento del sistema. El código es el siguiente:
public void syncMeHod () {beforemethod (); sincronizado (this) {mutexmethod ();} afterMethod ();} 3. Reduce el tamaño de la partícula de bloqueo
La reducción de la granularidad del bloqueo también es un medio efectivo para debilitar la competencia por los bloqueos de múltiples subprocesos. El escenario de uso típico de esta tecnología es la clase ConcurrentHashmap. En el hashmap ordinario, cada vez que se realiza una operación add () o get () en una colección, siempre se obtiene el bloqueo del objeto de recolección. Esta operación es completamente un comportamiento sincrónico porque el bloqueo está en todo el objeto de recolección. Por lo tanto, en alta concurrencia, la feroz competencia de bloqueo afectará el rendimiento del sistema.
Si ha leído el código fuente, debe saber que HashMap se implementa en una lista de matriz + vinculada. Concurrenthashmap divide todo el hashmap en varios segmentos (segmentos), y cada segmento es un subhashmap. Si necesita agregar una nueva entrada de tabla, no bloquea el hashmap. La línea de búsqueda de veinte años obtendrá la sección en la que la entrada de la tabla debe almacenarse de acuerdo con el húsico, y luego bloquear la sección y completar la operación PUT (). De esta manera, en un entorno múltiple, si múltiples hilos realizan operaciones de escritura al mismo tiempo, siempre que el elemento que se escribe no existe en el mismo segmento, se puede lograr un verdadero paralelismo entre los hilos. Para una implementación específica, espero que los lectores se tomen un tiempo para leer el código fuente de la clase ConcurrentHashmap, por lo que no lo describiré demasiado aquí.
4. Separación de bloqueo <Br /> un bloqueo de lectura y escritura ReadWriteLock mencionado anteriormente, luego la extensión de la separación de lectura y escritura es la separación del bloqueo. El código fuente de separación de bloqueos también se puede encontrar en el JDK.
clase pública LinkedBlokingqueue extiende AbstractQueue implementos Bloquingqueue, java.io.Serializable {/*bloqueo de bloqueo por toma, encuesta, etc./privado final reentrantlock takelock = new reentrantlock ();/** Wait Queue para una condición final de espera de Waiting*/Private Private Notempty = Takelock.newcondition ();/** Holding By Put, Oft*/private final Notempty NotEfty = Takelock.Newcondition ();/** Reentrantlock putlock = new ReentrantLock ();/** Wait Press para esperar*/condición final privada notfull = putlock.newcondition (); public e take () lanza interruptedException {ex; int c = -1; Conteo final AtomicInteger = this.count; Reentrantlock final Takelock = this.takelock; Takelock.lockInterruptable (); // No puede haber dos hilos para leer datos al mismo tiempo, intente {while (count.get () == 0) {// Si no hay datos disponibles, espere la notificación de put () notempty.await (); } x = dequeue (); // eliminar un elemento c = count.getAndDeCrement (); // tamaño menos 1 if (c> 1) noTempty.signal (); // notificar otras operaciones Take ()} Finalmente {Takelock.unlock (); // liberar bloqueo} if (c == capacidad) SignalNotFull (); // notificar la operación put (), ya hay retorno de espacio libre x;} public void put (e e) lanza interruptedException {if (e == null) tirar nueva nullPointerException (); // NOTA: La convención en todos los put/take/etc es para preestablecer var // retención de recuento negativo para indicar falla a menos que se establezca. int c = -1; Nodo <E> nodo = nuevo nodo (e); final reentrantlock putlock = this.putlock; Conteo final AtomicInteger = this.count; putlock.lockInterruptable (); // No puede haber dos hilos poner datos al mismo tiempo, intente { / * * Tenga en cuenta que el recuento se usa en la guardia de los camareros a pesar de que no está * protegido por el bloqueo. Esto funciona porque el recuento solo puede * disminuir en este punto (todas las demás puestas están cerradas * fuera por bloqueo), y nosotros (o alguna otra espera) estamos * firmados si alguna vez cambia de capacidad. Del mismo modo * para todos los demás usos del recuento en otros guardias de espera. */ while (count.get () == Capacidad) {// Si la cola está llena, espere notfull.await (); } enqueue (nodo); // une la cola c = count.getAndIrcrement (); // tamaño más 1 if (c + 1 <capacidad) notflull.signal (); // notifica a otros hilos si hay suficiente espacio} finalmente {putlock.unlock (); // libera el bloqueo} if (c == 0) SignalNotEmpty (); // Después de que la inserción es exitosa, notifique a la operación de leer datos} // otro código}Lo que debe explicarse aquí es que las funciones Take () y Put () son independientes entre sí, y no hay una relación de competencia de bloqueo entre ellas. Solo necesita competir por Takelock y Putlock dentro de los métodos respectivos de Take () y PUT (). Por lo tanto, la posibilidad de competencia de bloqueo se debilita.
5. Bloquear la grosería <Br /> La reducción mencionada anteriormente del tiempo de bloqueo y la granularidad se realiza para cumplir con el tiempo más corto para que cada hilo sostenga el bloqueo. Sin embargo, se debe comprender en granularidad. Si se solicita, sincronizado y liberado un bloqueo, consumirá recursos valiosos del sistema y aumentará la sobrecarga del sistema.
Lo que necesitamos saber es que cuando una máquina virtual encuentra una serie de solicitudes y lanzamientos continuos del mismo bloqueo, integrará todas las operaciones de bloqueo en una solicitud al bloqueo, reduciendo así la cantidad de solicitudes de bloqueo. Esta operación se llama grosería de bloqueo. Aquí hay una demostración del ejemplo de integración:
public void syncMeHod () {sincronizado (bloqueo) {método1 ();} sincronizado (bloqueo) {método2 ();}} la forma después de la integración jvm: public void syncMeHod () {sincronizado (bloqueo) {método1 (); método2 ();}}Por lo tanto, dicha integración brinda a nuestros desarrolladores un buen efecto de demostración al alcanzar la granularidad del bloqueo.
Lockless Paralel Computing <r /> Lo anterior ha pasado mucho tiempo hablando sobre el bloqueo, y también se menciona que el bloqueo traerá una sobrecarga de recursos adicional para un cambio de contexto. En alta concurrencia, la feroz competencia por "bloquear" puede convertirse en un cuello de botella del sistema. Por lo tanto, aquí se puede usar un método de sincronización que no es de bloqueo. Este método sin bloqueo aún puede garantizar que los datos y los programas mantengan la consistencia entre múltiples hilos en un entorno de altos concurrencia.
1. Sincronización sin bloqueo/Lockless
El método de sincronización sin bloqueo se refleja en el anterior ThreadLocal. Cada hilo tiene su propia copia independiente de variables, por lo que no hay necesidad de esperarse cuando se calculan en paralelo. Aquí, el autor recomienda principalmente un método de control de concurrencia sin bloqueo más importante basado en el algoritmo CAS de comparación e intercambio.
El proceso del algoritmo CAS: 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. El valor de V se establecerá en N solo cuando el valor V sea igual al valor E. Si el valor V es diferente del valor E, significa que otros hilos han realizado actualizaciones, y el hilo actual no hace nada. Finalmente, CAS devuelve el verdadero valor de la V. actual cuando opera CAS, se lleva a cabo con una actitud optimista, y siempre cree que puede completar con éxito la operación. Cuando múltiples hilos usan CAS para operar una variable al mismo tiempo, solo uno ganará y se actualizará con éxito, mientras que el resto de Junhui falla. 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. Según este principio, la operación CAS es oportuna sin bloqueos, y otros hilos también pueden detectar la interferencia al hilo actual y manejarlo adecuadamente.
2. Operación de peso atómico
El paquete Java.util.concurrent.atomic de JDK proporciona clases de operación atómica implementadas utilizando algoritmos sin bloqueo, y el código utiliza principalmente la implementación del código nativo subyacente. Los estudiantes interesados pueden continuar rastreando el código de nivel nativo. No publicaré la implementación del código de superficie aquí.
El siguiente utiliza principalmente un ejemplo para mostrar la brecha de rendimiento entre los métodos de sincronización ordinarios y la sincronización sin bloqueo:
clase pública testatomic {private static final int max_threads = 3; private static final int task_count = 3; private static final int Target_count = 100 * 10000; private AtomicInteger cuenta = new AtomITInTegeger (0); private int count = 0; sincronizado int inc () {++ contado sincronizado; {Nombre de cadena; Largo comienzo de inicio; Testatomic out; public Syncthread (testatomic o, long starttime) {this.out = o; this.starttime = starttime; } @Override public void run () {int v = out.inc (); while (v <target_count) {v = out.inc (); } Long EndTime = System.CurrentTimemillis (); System.out.println ("Syncthread gast:" + (endtime - starttime) + "ms" + ", v =" + v); }} public class AtomicThread implementos runnable {name de cadena; Largo comienzo de inicio; public AtomicThread (Long Starttime) {this.starttime = starttime; } @Override public void run () {int v = Account.IncrementAndget (); while (v <target_count) {v = cuenta.incrementandget (); } Long EndTime = System.CurrentTimemillis (); System.out.println ("AtomicThread Gasto:" + (endtime - starttime) + "ms" + ", v =" + v); }}@TestPublic void testSync () lanza interruptedException {EjecutorService exe = Executors.NewFixedThreadPool (max_threads); Long Starttime = System.CurrentTimemillis (); Syncthread sync = new Syncthread (this, starttime); for (int i = 0; i <task_count; i ++) {exe.submit (sync); } Thread.sleep (10000);}@testPublic void testatomic () lanza interruptedException {EjecutorService exe = Ejecutors.newFixedThreadPool (max_threads); Long Starttime = System.CurrentTimemillis (); AtomicThread Atomic = new AtomicThread (starttime); for (int i = 0; i <task_count; i ++) {exe.submit (atomic); } Thread.sleep (10000);}} Los resultados de la prueba son los siguientes:
testSync ():
Syncthread Gasto: 201ms, V = 1000002
Syncthread Gasto: 201ms, V = 1000000
Syncthread Gasto: 201ms, V = 1000001
testatomic ():
Gasto atómico: 43ms, v = 1000000
AtomicThread Gasto: 44ms, V = 1000001
Gasto atómico: 46ms, v = 1000002
Creo que tales resultados de las pruebas reflejarán claramente las diferencias de rendimiento entre el bloqueo interno y los algoritmos de sincronización sin bloqueo. Por lo tanto, el autor recomienda considerar directamente esta clase atómica bajo atómica.
Conclusión
Finalmente, he resuelto las cosas que quiero expresar. De hecho, todavía hay algunas clases como CountdownLatch que no se han mencionado. Sin embargo, lo que se mencionó anteriormente es definitivamente el núcleo de la programación concurrente. Quizás algunos lectores puedan ver muchos puntos de conocimiento en Internet, pero todavía creo que solo por comparación se puede encontrar el conocimiento en un escenario de uso adecuado. Por lo tanto, esta es también la razón por la cual el editor ha compilado este artículo, y espero que este artículo pueda ayudar a más estudiantes.
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.