El mejor enfoque para usar en Java 6 para una lista a la que se accede simultáneamente

StackOverflow https://stackoverflow.com/questions/207829

  •  03-07-2019
  •  | 
  •  

Pregunta

Tengo un objeto de lista al que se accede mediante varios subprocesos. En su mayoría hay un hilo y, en algunas condiciones, dos hilos, que actualiza la lista. Hay uno a cinco hilos que pueden leerse de esta lista, dependiendo de la cantidad de solicitudes de usuario que se procesen. La lista no es una cola de tareas a realizar, es una lista de objetos de dominio que se recuperan y actualizan simultáneamente.

Ahora hay varias formas de hacer que el acceso a esta lista sea seguro para subprocesos:
-utilizar bloque sincronizado
-utiliza el Bloqueo normal (es decir, las operaciones de lectura y escritura comparten el mismo bloqueo)
-usar ReadWriteLock
-utilizar una de las nuevas clases de colección ConcurrentBLABLBA

Mi pregunta:
¿Cuál es el enfoque óptimo de uso, dado que las secciones críticas normalmente no contienen muchas operaciones (en su mayoría solo añadiendo / eliminando / insertando o obteniendo elementos de la lista)? ¿Puedes recomendar otro enfoque, que no esté en la lista de arriba?

Algunas restricciones
El rendimiento óptimo es crítico, el uso de la memoria no es tanto. - debe ser una lista ordenada (actualmente sincronizada en una ArrayList ), aunque no una lista ordenada (es decir, no clasificada utilizando Comparable o Comparator, pero según el orden de inserción)
-la lista será grande, con hasta 100000 objetos de dominio, por lo que no es posible utilizar algo como CopyOnWriteArrayList
-Las secciones ciríticas de escritura / actualización son típicamente muy rápidas, haciendo simplemente agregar / eliminar / insertar o reemplazar (configurar)
-las operaciones de lectura realizarán principalmente una llamada elementAt (índice) la mayor parte del tiempo, aunque algunas operaciones de lectura podrían hacer una búsqueda binaria, o indexOf (elemento)
-no se realiza ninguna iteración directa sobre la lista, aunque la operación como indexOf (..) atravesará la lista

¿Fue útil?

Solución

¿Tienes que usar una lista secuencial? Si una estructura de tipo de mapa es más apropiada, puede usar un ConcurrentHashMap . Con una lista, un ReadWriteLock es probablemente la forma más efectiva.

Editar para reflejar la edición de OP: ¿Búsqueda binaria en el orden de inserción? ¿Guarda una marca de tiempo y la usa para comparar, en su búsqueda binaria? Si es así, puede usar la marca de tiempo como clave y ConcurrentSkipListMap como contenedor (que mantiene el orden de las claves).

Otros consejos

¿Qué están haciendo los hilos de lectura? Si están iterando sobre la lista, entonces realmente necesita asegurarse de que nadie toque la lista durante todo el proceso de iteración, de lo contrario podría obtener resultados muy extraños.

Si puede definir con precisión qué semántica necesita, debería ser posible resolver el problema, pero es posible que tenga que escribir su propio tipo de colección para hacerlo de manera adecuada y eficiente. Alternativamente, CopyOnWriteArrayList puede Bueno, sé lo suficientemente bueno - si es potencialmente caro. Básicamente, cuanto más pueda atar sus requisitos, más eficiente será.

No sé si esta es una solución posible para el problema, pero ... para mí tiene sentido usar un administrador de base de datos para almacenar esa enorme cantidad de datos y dejar que administre las transacciones

Segundo La sugerencia de Telcontar de una base de datos, ya que en realidad están diseñadas para administrar esta escala de datos y negociar entre subprocesos, mientras que las colecciones en memoria no.

Usted dice que los datos están en una base de datos en el servidor, y la lista local en los clientes es por el bien de la interfaz de usuario. No debería tener que mantener todos los 100000 elementos en el cliente a la vez, o realizar ediciones tan complicadas. Me parece que lo que quiere en el cliente es un caché ligero en la base de datos.

Escriba un caché que almacene solo el subconjunto actual de datos en el cliente a la vez. Este caché de cliente no realiza ediciones complejas de subprocesos múltiples en sus propios datos; en su lugar, alimenta todas las ediciones a través del servidor y escucha las actualizaciones. Cuando los datos cambian en el servidor, el cliente simplemente olvida los datos antiguos y los vuelve a cargar. Solo un hilo designado puede leer o escribir la colección en sí. De esta manera, el cliente simplemente duplica las ediciones que ocurren en el servidor, en lugar de necesitar ediciones complicadas.

Sí, esta es una solución bastante complicada. Los componentes de la misma son:

  • Un protocolo para cargar un rango de datos, dicen los artículos 478712 a 478901, en lugar de todo.
  • Un protocolo para recibir actualizaciones sobre los datos modificados
  • Una clase de caché que almacena elementos por su índice conocido en el servidor
  • Un subproceso que pertenece a ese caché que se comunicó con el servidor. Este es el único hilo que escribe en la propia colección
  • Un subproceso que pertenece a esa memoria caché que procesa las devoluciones de llamada cuando se recuperan los datos
  • Una interfaz que los componentes de la interfaz de usuario implementan para permitirles recibir datos cuando se han cargado

En la primera puñalada, los huesos de este caché pueden tener este aspecto:

class ServerCacheViewThingy {
    private static final int ACCEPTABLE_SIZE = 500;
    private int viewStart, viewLength;
    final Map<Integer, Record> items
            = new HashMap<Integer, Record>(1000);
    final ConcurrentLinkedQueue<Callback> callbackQueue
            = new ConcurrentLinkedQueue<Callback>();

    public void getRecords (int start, int length, ViewReciever reciever) {
        // remember the current view, to prevent records within
        // this view from being accidentally pruned.
        viewStart = start;
        viewLenght = length;

        // if the selected area is not already loaded, send a request
        // to load that area
        if (!rangeLoaded(start, length))
            addLoadRequest(start, length);

        // add the reciever to the queue, so it will be processed
        // when the data has arrived
        if (reciever != null)
            callbackQueue.add(new Callback(start, length, reciever));
    }

    class Callback {
        int start;
        int length;
        ViewReciever reciever;
        ...
    }

    class EditorThread extends Thread {

        private void prune () {
            if (items.size() <= ACCEPTABLE_SIZE)
                return;
            for (Map.Entry<Integer, Record> entry : items.entrySet()) {
                int position = entry.key();
                // if the position is outside the current view,
                // remove that item from the cache
                ...
            }
        }

        private void markDirty (int from) { ... }

        ....
    }

    class CallbackThread extends Thread {
        public void notifyCallback (Callback callback);
        private void processCallback (Callback) {
            readRecords
        }
    }
}

interface ViewReciever {
    void recieveData (int viewStart, Record[] records);
    void recieveTimeout ();
}

Hay muchos detalles que tendrás que completar por ti mismo, obviamente.

Puede usar un contenedor que implemente sincronización:

import java.util.Collections;
import java.util.ArrayList;

ArrayList list = new ArrayList();
List syncList = Collections.synchronizedList(list);

// make sure you only use syncList for your future calls... 

Esta es una solución fácil. Intentaría esto antes de recurrir a soluciones más complicadas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top