[Herramienta concurrente de Java clase-colaboración] contenedor concurrente


Los contenedores en Java se dividen principalmente en cuatro categorías: Lista, Mapa, Conjunto y Cola, pero no todos los contenedores son seguros para subprocesos, como ArrayList, no es seguro para subprocesos

1. Entonces, ¿cómo puede convertir ArrayList en un contenedor seguro para subprocesos?

De hecho, la idea es muy simple, simplemente encapsule el contenedor no seguro para subprocesos dentro del objeto y luego controle el método de acceso.

Tomemos ArrayList como ejemplo para ver cómo hacer que sea seguro para subprocesos.

SafeArrayList<T>{
  List<T> c = new ArrayList<>(); //封装ArrayList
  //控制访问路径
  synchronized T get(int idx){
    return c.get(idx);
  }
  synchronized  void add(int idx, T t) {
    c.add(idx, t);
  }
  synchronized  boolean addIfNotExist(T t){
    if(!c.contains(t)) {
      c.add(t);
      return true;
    }
    return false;
  }
}

Encapsule ArrayList arriba y luego use sincronizado para bloquear el método de acceso ArrayList, es decir, solo un subproceso puede acceder a ArrayList, que se vuelve seguro para subprocesos. Entonces, ¿no pueden todos los contenedores que no sean seguros para subprocesos?

2. Sincronizar contenedores

El SDK de Java piensa en la situación anterior y proporciona este método.

List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());

Nota: No es un problema para un contenedor seguro para subprocesos llamar a un solo método, pero cuando se llaman múltiples métodos, es decir, al combinar operaciones, debe prestar atención a las condiciones de carrera.
Justo como usar un iterador para atravesar el contenedor.

List list = Collections.synchronizedList(new ArrayList());
Iterator i = list.iterator(); 
while (i.hasNext())
  foo(i.next());

Aunque el contenedor es seguro para subprocesos y no hay problemas para acceder a un único método, hay un problema de seguridad cuando se combina. Cuando el subproceso T1 accede a i.hasNext () y el subproceso T2 accede a i.next (), el subproceso T1 accede a i.next ( ) Hay un problema
Enfoque correcto:

List list = Collections. synchronizedList(new ArrayList());
synchronized (list) {  //加锁之后只能一个线程访问这两个组合操作了
  Iterator i = list.iterator(); 
  while (i.hasNext())
    foo(i.next());
}    

Los contenedores de sincronización proporcionados por Java también son Vector, Stack y Hashtable, que también se implementan de forma sincronizada. El recorrido de estos tres contenedores también debe bloquearse para garantizar la exclusión mutua.

3. Contenedores concurrentes

El llamado contenedor seguro para subprocesos antes de java1.5 se refiere al contenedor síncrono . Debido a que la implementación sincronizada garantiza la exclusión mutua, la serialización es demasiado alta y el rendimiento es demasiado pobre.
Más tarde, después de la versión Java 1.5, se proporcionó un contenedor con mayor rendimiento, que generalmente se denomina contenedor concurrente .

Los contenedores concurrentes siguen siendo esas cuatro categorías:
Inserte la descripción de la imagen aquí

3.1 Lista

La única clase de implementación en List es CopyOnWriteArrayList. CopyOnWrite, como su nombre lo indica, hará una nueva copia de la variable compartida al escribir. La ventaja de esto es que la operación de lectura está completamente libre de bloqueos.

  • Combine la siguiente figura para comprender el principio de CopyOnWriteArrayList.
    CopyOnWriteArrayList mantiene una matriz internamente, la matriz miembro variable apunta a la matriz y todas las operaciones de lectura se basan en esta matriz. Si hay una operación de escritura durante el desplazamiento, como agregar el elemento 9, copiará una copia de la matriz original, y luego escribirá la operación de matriz de copia, después de la ejecución, apunte la matriz a la matriz de copia.
    Inserte la descripción de la imagen aquí

  • Escenario de aplicación: CopyOnWriteArrayList solo es adecuado para escenarios con muy pocas operaciones de escritura, y puede tolerar inconsistencias a corto plazo en lectura y escritura. Por ejemplo, en la operación anterior, los elementos escritos no se pueden recorrer de inmediato.

  • Nota: El iterador CopyOnWriteArrayList es de solo lectura y no admite la adición, eliminación o modificación. Debido a que el iterador solo atraviesa una instantánea, y agregar, eliminar y modificar instantáneas no tiene sentido.

3.2 Mapa

Las dos implementaciones de la interfaz Map son ConcurrentHashMap y ConcurrentSkipListMap.

  • Diferencia: las claves de ConcurrentHashMap no están ordenadas. Se ordenan las claves de ConcurrentSkipListMap.
  • La siguiente tabla resume los requisitos de clave y valor de las clases de implementación relacionadas con el Mapa.
    Inserte la descripción de la imagen aquí
  • Rendimiento: SkipList en ConcurrentSkipListMap es una estructura de datos, el chino generalmente se traduce como "tabla de salto". La complejidad de tiempo promedio de las operaciones de inserción, eliminación y consulta de la tabla de salto es O (log n), que en teoría no está relacionada con el número de subprocesos concurrentes, por lo que en el caso de una concurrencia muy alta, si no está satisfecho con el rendimiento de ConcurrentHashMap, puede Prueba ConcurrentSkipListMap.

3.3 Set

Las dos implementaciones de la interfaz Set son CopyOnWriteArraySet y ConcurrentSkipListSet,

  • Para los escenarios de uso, puede consultar CopyOnWriteArrayList (leer más y escribir menos) y ConcurrentSkipListMap (clave ordenada) descritos anteriormente. Sus principios son los mismos, por lo que no los repetiré aquí.

3.4 Cola

Las colas en paquetes concurrentes de Java se pueden clasificar en dos dimensiones:

  • Bloqueo y no bloqueo: el bloqueo significa que la cola está llena, la operación en cola está bloqueada, la cola está vacía y la operación en cola está bloqueada. Las palabras clave de bloqueo se utilizan en paquetes concurrentes de Java para indicar colas de bloqueo.
  • Unipolar y doble: solo puede entrar al equipo al final del equipo, el primer equipo fuera; el doble final es el final del equipo y el capital del equipo puede ingresar al equipo. Las colas de un solo extremo se marcan con Cola y las colas de dos extremos se marcan con Deque

Las dos dimensiones se combinan en cuatro categorías:

  • Cola de bloqueo de un solo extremo: se implementan ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, LinkedTransferQueue, PriorityBlockingQueue y DelayQueue.
  1. ArrayBlockingQueue: en general, una cola de matriz se mantendrá internamente.
  2. LinkedBlockingQueue: una cola de lista vinculada generalmente se mantiene internamente.
  3. SynchronousQueue: no contiene la cola, el modo de consumidor productor, la operación de cola de hilo del productor debe esperar la operación de cola de hilo del consumidor.
  4. LinkedTransferQueue: integra las funciones de LinkedBlockingQueue y SynchronousQueue, y su rendimiento es mejor que LinkedBlockingQueue.
  5. PriorityBlockingQueue admite la eliminación de cola según la prioridad.
  6. DelayQueue admite el retraso de la cola.
  • Cola de bloqueo de doble extremo: su implementación es LinkedBlockingDeque.
    Inserte la descripción de la imagen aquí
  • Cola sin bloqueo de un solo extremo: su implementación es ConcurrentLinkedQueue.
  • Cola sin bloqueo de doble extremo: su implementación es ConcurrentLinkedDeque.

Nota: Solo el ArrayBlockingQueue y LinkedBlockingQueue de estas colas son compatibles, por lo que al utilizar otras colas no acotadas, debe tener en cuenta si existen peligros ocultos que causen OOM.
No se recomienda utilizar colas ilimitadas en el trabajo general.

Referencia: Geek Time
Más: Deng Xin

Publicado 34 artículos originales · Me gusta0 · Visitas 1089

Supongo que te gusta

Origin blog.csdn.net/qq_42634696/article/details/105173018
Recomendado
Clasificación