Programación concurrente de Java (2): herramienta avanzada Java multithreading-java.util.concurrent

Clase avanzada de control multiproceso

Java1.5 proporciona un paquete multiproceso muy eficiente y práctico: java.util.concurrent, que proporciona una gran cantidad de herramientas avanzadas para ayudar a los desarrolladores a escribir programas Java multiproceso eficientes, fáciles de mantener y claramente estructurados.

Clase ThreadLocal

La clase ThreadLocal se utiliza para guardar variables independientes de subprocesos. Para una clase de hilo (heredada de Thread)

Cuando se usa ThreadLocal para mantener variables, ThreadLocal proporciona una copia independiente de la variable para cada subproceso que usa la variable, por lo que cada subproceso puede cambiar de forma independiente su propia copia sin afectar las copias correspondientes de otros subprocesos . Comúnmente utilizado para el control de inicio de sesión de usuarios, como registrar información de la sesión.

Implementación: cada hilo contiene una variable de tipo TreadLocalMap (esta clase es un mapa liviano, la función es la misma que el mapa, la diferencia es que las entradas se colocan en el depósito en lugar de una lista vinculada de entradas. La función sigue siendo un mapa .) En sí mismo es la clave y el objetivo es el valor.

Los métodos principales son get () y set (T a). Después de configurar, se mantiene un threadLocal -> a en el mapa y se devuelve a al obtener. ThreadLocal es un contenedor especial.

Clases atómicas (AtomicInteger, AtomicBoolean...)

Si usa una clase contenedora atómica como atomicInteger, o usa su propia operación atómica garantizada, es equivalente a sincronizado

AtomicInteger.compareAndSet(int expect,int update)//El valor de retorno es booleano

Referencia atómica

Para AtomicReference, el objeto puede tener propiedades perdidas, es decir, oldObject == actual, pero oldObject.getPropertyA! = current.getPropertyA.

En este momento, AtomicStampedReference resulta útil. Esta también es una idea muy común, es decir, agregar el número de versión

Clase de bloqueo 

bloqueo: en el paquete java.util.concurrent. Hay tres implementaciones:

  1. Bloqueo reentrante

  2. ReentrantReadWriteLock.ReadLock

  3. ReentrantReadWriteLock.WriteLock

El objetivo principal es el mismo que el de sincronizado: ambas son tecnologías creadas para resolver problemas de sincronización y manejar disputas de recursos. La funcionalidad es similar pero existen algunas diferencias.

Las diferencias son las siguientes:

  1. El bloqueo es más flexible y puede definir libremente el orden de desbloqueo de los grilletes de múltiples bloqueos (primero se debe agregar sincronizado y luego desbloquear)

  2. Proporciona una variedad de esquemas de bloqueo, bloqueo de bloqueo, trylock sin bloqueo, lockInterruptily interrumpible y versión trylock con tiempo de espera.

  3. Esencialmente lo mismo que el bloqueo del monitor (es decir, sincronizado)

  4. Un gran poder conlleva una mayor responsabilidad, y es necesario controlar el bloqueo y el desbloqueo, de lo contrario puede producirse un desastre.

  5. Combinación con clase de condición.

  6. Mayor rendimiento, comparación de rendimiento entre sincronizado y Lock, como se muestra a continuación:

Uso de ReentrantLock

El significado de reentrada es que el hilo que sostiene el candado puede continuar manteniéndolo y el candado debe liberarse un número igual de veces antes de que realmente se libere.

java.util.concurrent.locks.Lock privado lock = new ReentrantLock(); 
public void método() {	 
	intentar {		 
		lock.lock(); //Obtener bloqueo, bloque sincronizado 
	} finalmente {		 
		lock.unlock();/ /liberar bloquear 
	} 
}

  • ReentrantLock es más poderoso que sincronizado, lo que se refleja principalmente en:

  • ReentrantLock tiene una variedad de estrategias justas.

  • ReentrantLock se puede adquirir condicionalmente al adquirir el bloqueo y se puede establecer el tiempo de espera, evitando efectivamente el punto muerto.

  • Como tryLock() y tryLock(tiempo de espera prolongado, unidad TimeUnit)

  • ReentrantLock puede obtener diversa información sobre la cerradura y se utiliza para monitorear varios estados de la cerradura.

  • ReentrantLock puede implementar de manera flexible múltiples notificaciones, es decir, la aplicación de Condición.

Bloqueo justo y bloqueo injusto

ReentrantLock tiene por defecto un bloqueo injusto, lo que permite a los subprocesos "adelantarse a la cola" para adquirir el bloqueo. El bloqueo justo significa que los subprocesos adquieren bloqueos en el orden solicitado, similar a la estrategia FIFO.

uso de cerraduras

  1. lock () adquiere el bloqueo de forma bloqueante y solo procesa la información de interrupción después de adquirir el bloqueo.

  2. lockInterruptably() adquiere el bloqueo de forma bloqueante, maneja la información de interrupción inmediatamente y genera una excepción.

  3. tryLock() intenta adquirir el bloqueo. Independientemente del éxito o el fracaso, inmediatamente devolverá verdadero o falso. Tenga en cuenta que incluso si el bloqueo se ha configurado para utilizar una estrategia de clasificación justa, tryLock() aún puede activar la equidad para saltar. la cola y adelantarse a ella. Si desea respetar la configuración justa de este bloqueo, utilice tryLock(0, TimeUnit.SECONDS), que es casi equivalente (también detecta interrupciones).

  4. tryLock (tiempo de espera prolongado, unidad TimeUnit) adquiere el bloqueo de forma bloqueante dentro del tiempo de espera, devuelve verdadero con éxito, devuelve falso cuando se agota el tiempo de espera, procesa la información de interrupción inmediatamente y genera una excepción.

Si desea utilizar un tryLock cronometrado que permita romper cerraduras justas, puede combinar las formas cronometradas y no cronometradas:

if (lock.tryLock() || lock.tryLock(tiempo de espera, unidad) ) { ... }

java.util.concurrent.locks.ReentrantLock privado lock = new ReentrantLock(); 
public void testMethod() {	 
	try {		 
		if (lock.tryLock(1, TimeUnit.SECONDS)) { 
			//obtener bloqueo, bloque de sincronización 
		} else {		 
			/ /El bloqueo no fue adquirido 
		} 
	} catch (InterruptedException e) { 
		e.printStackTrace(); 
	} finalmente { 
		if (lock.isHeldByCurrentThread()) 
		//Si el hilo actual mantiene el bloqueo, libera el bloqueo 
			lock.unlock (); 
	} 
}

Uso de la condición

Los bloqueos pueden crear condiciones para implementar un mecanismo de notificación multicanal.

Los métodos con await, signal y signal son similares a esperar/notificar y deben llamarse después de adquirir el bloqueo.

java.util.concurrent.locks.Lock final privado = new ReentrantLock(); 
java.util.concurrent.locks.Condition final privado = lock.newCondition(); 
public void await() {	 
	intentar {		 
		lock.lock() ; //Obtener condición de bloqueo.await 
		(); //Esperar señal de comunicación de condición, liberar bloqueo de condición 
		//Recibir comunicación de condición 
	} catch (InterruptedException e) { 
		e.printStackTrace(); 
	} finalmente { 
		lock.unlock() ;/ /Liberar bloqueo de objeto 
	} 
}

Uso de ReentrantReadWriteLock

ReentrantReadWriteLock es una extensión adicional de ReentrantLock que implementa el bloqueo de lectura readLock () (bloqueo compartido) y el bloqueo de escritura writeLock () (bloqueo exclusivo) para realizar la separación de lectura y escritura. La lectura y la lectura no se excluyen mutuamente, solo la lectura y la escritura, la escritura y la lectura, y la escritura y la escritura son mutuamente excluyentes, lo que mejora el rendimiento de la lectura y la escritura.

Leer ejemplo de bloqueo:

java.util.concurrent.locks.ReadWriteLock final privado lock = new ReentrantReadWriteLock(); 
public void método() {	 
	intentar {		 
		lock.readLock().lock();//obtener bloqueo de lectura readLock, bloque sincronizado 
	} finalmente {		 
		lock . readLock().unlock();//Libera el bloqueo de lectura readLock 
	} 
}

Ejemplo de bloqueo de escritura:

java.util.concurrent.locks.ReadWriteLock final privado lock = new ReentrantReadWriteLock(); 
método public void() {	 
	intentar {		 
		lock.writeLock().lock(); //obtener bloqueo de escritura writeLock, bloque sincronizado 
	} finalmente { 
		lock . writeLock().unlock(); //Libera el bloqueo de escritura writeLock 
	} 
}

Clase de contenedor

Descripción general de contenedores sincrónicos y asincrónicos

Sincronizar contenedores

Incluye dos partes:

  • Uno es Vector y Hashtable de los primeros JDK;

  • Uno es su contenedor homólogo, la clase contenedora de sincronización agregada en JDK1.2, que se crea utilizando el método de fábrica Collections.synchronizedXxx.

Map<String, Integer> hashmapSync = Collections.synchronizedMap(new HashMap<>());

Los contenedores síncronos son seguros para subprocesos y solo un subproceso puede acceder al estado del contenedor a la vez .

Sin embargo, en algunos escenarios, es posible que sea necesario bloquear para proteger las operaciones compuestas .

Operaciones de clases compuestas como: agregar, eliminar, iterar, saltar y operaciones condicionales.

Estas operaciones compuestas pueden presentar un comportamiento inesperado cuando varios subprocesos modifican el contenedor al mismo tiempo.

El más clásico es ConcurrentModificationException.

La razón es que cuando el contenedor itera, el contenido se modifica al mismo tiempo, porque el diseño inicial del iterador no consideró la cuestión de la modificación concurrente.

El mecanismo subyacente no es más que usar la palabra clave sincronizada tradicional para sincronizar cada método público, de modo que solo un hilo pueda acceder al estado del contenedor a la vez. Obviamente, esto no satisface las necesidades de alta concurrencia de nuestra era de Internet actual: si bien garantiza la seguridad de los subprocesos, también debe tener un rendimiento suficientemente bueno.

contenedor concurrente

En comparación con los contenedores de sincronización Collections.synchronizedXxx (), etc., el contenedor concurrente introducido en util.concurrent resuelve principalmente dos problemas:

  1. Diseñe de acuerdo con escenarios específicos, intente evitar la sincronización y proporcione concurrencia .

  2. Define algunas operaciones compuestas seguras para la concurrencia y garantiza que las operaciones iterativas en un entorno concurrente no saldrán mal .

Cuando el contenedor en util.concurrent está iterando, no es necesario encapsularlo en sincronizado y puede garantizar que no se generará ninguna excepción, pero es posible que no sean los datos "más recientes y actuales" que ve cada vez.

Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

ConcurrentHashMap reemplaza el mapa sincronizado (Collections.synchronized(new HashMap())).

Como todos sabemos, HashMap se almacena en segmentos según los valores hash. Synchronous Maps bloqueará todo el mapa durante la sincronización , mientras que ConcurrentHashMap introduce definiciones de segmentos al diseñar el almacenamiento. Durante la sincronización, solo necesita bloquear según los valores hash. Solo concéntrese en el párrafo donde se encuentra el valor hash, lo que mejora enormemente el rendimiento . ConcurrentHashMap también agrega soporte para operaciones compuestas comunes, como "si no se agrega": putIfAbsent(), reemplazo: reemplazar(). Estas dos operaciones son operaciones atómicas. Tenga en cuenta que ConcurrentHashMap debilita los métodos size() e isEmpty() y los utiliza lo menos posible en situaciones concurrentes para evitar posibles bloqueos (por supuesto, también es posible obtener el valor sin bloquear, si el número de mapas no lo hace). cambiar).

CopyOnWriteArrayList y CopyOnWriteArraySet reemplazan a List y Set respectivamente. Se utilizan principalmente para reemplazar List y Set sincrónicos cuando las operaciones transversales son las principales. Esta es la idea mencionada anteriormente: el proceso de iteración debe garantizar que no se produzcan errores. Además del bloqueo, Otra forma es "clonar" el objeto contenedor. --- Las desventajas también son obvias: ocupa memoria y, en última instancia, los datos son consistentes, pero los datos pueden no ser consistentes en tiempo real, generalmente se usa en escenarios concurrentes con más lectura y menos escritura.

  • ConcurrentSkipListMap puede reemplazar a SoredMap en concurrencia eficiente (como TreeMap envuelto con Collections.synchronzedMap).

  • ConcurrentSkipListSet puede reemplazar a SoredSet en concurrencia eficiente (como TreeMap envuelto con Collections.synchronzedSet).

  • ConcurrentLinkedQuerue es una cola de primero en entrar, primero en salir. Es una cola sin bloqueo. Tenga cuidado al utilizar isEmpty en lugar de size();

Uso del bloqueo CountDownLatch

Gestión

El concepto de clase de gestión es relativamente general y se utiliza para gestionar subprocesos. No es multiproceso en sí mismo, pero proporciona algunos mecanismos para utilizar las herramientas anteriores para realizar alguna encapsulación.

Las clases de gestión que vale la pena mencionar y que aprendí: ThreadPoolExecutor y la clase de gestión a nivel de sistema ThreadMXBean en el marco JMX.

ThreadPoolEjecutor

Si no conoce esta clase, debe conocer el ExecutorService mencionado anteriormente, que es muy conveniente para abrir su propio grupo de subprocesos:

ExecutorService e = Executors.newCachedThreadPool(); 
    ExecutorService e = Executors.newSingleThreadExecutor(); 
    ExecutorService e = Executors.newFixedThreadPool(3); 
    // El primero es un grupo de subprocesos de tamaño variable, que asigna subprocesos según el número de tareas, 
    // El segundo es un grupo de subprocesos únicos, equivalente a FixedThreadPool(1) 
    // El tercero es un grupo de subprocesos de tamaño fijo. 
    // Luego ejecuta 
    e.execute(new MyRunnableImpl());

Esta clase se implementa internamente a través de ThreadPoolExecutor. Dominar esta clase ayuda a comprender la gestión de grupos de subprocesos. En esencia, son varias versiones de implementación de la clase ThreadPoolExecutor.

Artículo de referencia:

Transcripción general de la programación concurrente multiproceso de Java  https://www.cnblogs.com/yw0219/p/10597041.html

Para subprocesos múltiples en Java, solo necesita leer este artículo https://juejin.im/entry/57339fe82e958a0066bf284f

Vuelva a imprimir el artículo " Programación concurrente de Java (2): subprocesos múltiples de Java - herramientas avanzadas java.util.concurrent ",
indique la fuente: Programación concurrente de Java (2): subprocesos múltiples de Java - herramientas avanzadas java.util.concurrent - java Centrarse en la revisión Contenido y explicación detallada de los conceptos básicos: sitio web personal de Zhou Junjun.

Supongo que te gusta

Origin blog.csdn.net/u012244479/article/details/130049701
Recomendado
Clasificación