2011-02-07 15 views
13

Tengo una lista, que se debe utilizar en contexto seguro para subprocesos o en uno no seguro para subprocesos. Cuál será, es imposible determinar de antemano.Seguridad de subprocesos Java de la lista

En el caso especial de este tipo, cada vez que entra en la lista de contexto no seguro para subprocesos, envuelvo usando

Collections.synchronizedList (...)

Pero no quiero para envolverlo, si no entra en contexto no seguro para subprocesos. Es decir, porque la lista es enorme y se usa intensamente.

He leído acerca de Java, que su política de optimización es estricta sobre multi-threading: si no sincroniza su código correctamente, no se garantiza que se ejecute correctamente en contexto entre hilos, puede reorganizar el código significativamente, proporcionando coherencia en el contexto de un solo hilo (ver http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.3). F.e.,

op1; op2; op3;

puede ser reorganizada en

op3; op2; op1;

, si produce el mismo resultado (en contexto de un hilo).

Ahora me pregunto, si yo

  1. llenar mi lista antes de envolverlo por synchronizedList,

  2. luego envolverlo,

  3. a continuación, utilizar por subproceso diferente

, - hay una posibilidad, ese hilo diferente verá esta lista se llenó solo parcialmente o no se llenó en absoluto? ¿Podría JVM posponer (1) hasta después (3)? ¿Hay una manera correcta y rápida de hacer que la Lista (grande) no sea segura para subprocesos para convertirse en segura para subprocesos?

+2

¿Tiene usted intenté envolver tu lista en una lista sincronizada y resultó ser demasiado lenta? – biziclop

+2

"nihilistic"? .. – DJClayworth

Respuesta

12

Cuando das su lista para otro hilo por medio de hilo de seguridad (por ejemplo, utilizando un bloque sincronizado, una variable volátil o un AtomicReference), se garantiza que el segundo hilo ve toda la lista en el estado que fue cuando se transfiere (o cualquier estado posterior, pero no un estado anterior).

Si no lo cambia después, tampoco necesita su synchronizedList.


Editar (después de algunos comentarios, hacer copias de seguridad de mi reclamo):

asumo la siguiente:

  • tenemos una variable volátil list.

    volatile List<String> list = null; 
    
  • Tema A:

    1. crea una lista L y L se llena con elementos.
    2. conjuntos list para apuntar a L (esto significa escribe L a list)
    3. no hace modificaciones adicionales en L.

    fuente de la muestra:

    public void threadA() { 
        List<String> L = new ArrayList<String>(); 
        L.add("Hello"); 
        L.add("World"); 
        list = l; 
    } 
    
  • Tema B:

    1. lee K de list
    2. itera sobre K, imprimiendo los elementos.

    fuente de la muestra:

    public void threadB() { 
        List<String> K = list; 
        for(String s : K) { 
         System.out.println(s); 
        } 
    } 
    
  • Todos los otros hilos no tocan la lista.

Ahora tenemos esto:

  • las acciones 1-A y 2-A en Hilo A están clasificadas por program order modo 1 está antes 2.
  • La acción 1-B y 2 -B en el subproceso B están ordenados por program order así que 1 viene antes 2.
  • La acción 2-A en el subproceso A y la acción 1-B en el subproceso están ordenados por synchronization order, por lo que 2-A viene antes de 1-B, desde

    Una escritura en una variable volátil (§8.3.1.4) v sincroniza con todas las lecturas subsiguientes de v por cualquier subproceso (donde la subsecuente se define según el orden de sincronización).

  • El happens-before -order es el cierre transitivo de las órdenes de programa de los hilos individuales y el orden de sincronización. Así tenemos:

    1-A-ocurre antes del 2-A-ocurre antes del 1-B-ocurre antes del 2-B

    y por lo tanto 1-A-ocurre antes del 2-B.

  • Por último,

    Si una acción sucede-antes de que otro, entonces la primera es visible desde y pedidas antes de la segunda.

Así que nuestro hilo iteración realmente puede ver toda la lista, y no sólo algunas partes de ella. Por lo tanto, transmitir la lista con una sola variable volátil es suficiente, y no necesitamos sincronización en este caso simple.


Una edición más (en este caso, ya que tengo más libertad que en el formato de los comentarios) sobre el programa de fin de hilo A. (también añadido un código de ejemplo anterior.)

Desde el JLS (sección program order):

Entre todas las acciones entre hilos realizadas por cada t hilo, el orden del programa de T es un orden total que refleja el orden en que estas acciones sería realizado de acuerdo con la semántica intra-hilo de t.

Entonces, ¿qué son la semántica intra-hilo de rosca A?

Algunos paragraphs above:

El modelo de memoria determina qué valores se pueden leer en todos los puntos en el programa. Las acciones de cada hebra en aislamiento deben comportarse como se rigen por la semántica de esa hebra, con la excepción de que los valores que ve cada lectura son determinados por el modelo de memoria. Cuando nos referimos a esto, decimos que el programa obedece a semántica intrahilo. La semántica intrahilo es la semántica de los programas de un solo subproceso , y permite la predicción completa del comportamiento de un subproceso basado en los valores observados por las acciones de lectura dentro del subproceso. Para determinar si las acciones de thread t en una ejecución son legales, simplemente evaluamos la implementación de thread t, ya que se realizaría en un único contexto de subprocesamiento, como se define en el resto de esta especificación.

El resto de esta especificación incluye section 14.2 (Blocks):

Un bloque se ejecuta mediante la ejecución de cada una de la declaración de variable local declaraciones y otras declaraciones en orden desde el primero al último (de izquierda a derecha) .

Por lo tanto, el orden del programa es de hecho el orden en que las declaraciones/expresiones se dan en el código fuente del programa.

Por lo tanto, en nuestra fuente de ejemplo, las acciones de memoria crear un nuevo ArrayList, complemento "Hola", añadir "Mundial" y asignan a list (los tres primeros consisten en más subacciones) de hecho están en esta orden de programa.

(La máquina virtual no tiene que ejecutar las acciones en este orden, pero esta orden del programa sigue contribuyendo a la sucede antes orden, y por lo tanto a la visibilidad a otros hilos.)

+1

Si declaro que esta Lista es volátil, ¿no garantizo solo el hecho de que ** la referencia ** a esta Lista es segura para subprocesos? Es decir, no garantiza que las referencias guardadas dentro de esa lista sean volátiles por cascada ... – Andrey

+0

@Andrey: la asignación volátil garantiza la visibilidad de todo, incluidas las referencias dentro de la lista. la advertencia importante, por supuesto, es que esta lista no debe ser modificada después de la asignación volátil. – jtahlborn

+0

Una variable volátil asegura que cuando un hilo lo lee, ve _everything_ que otro hilo ha escrito antes de que este otro hilo escriba este valor en la variable volátil (no se limita a los objetos accesibles desde la variable en sí; podría ser un booleano, incluso) . Así que asegúrese de asignar a esta variable volátil _después_ de llenar la lista, no antes ni durante. –

0

Observe cómo AtomicInteger (y similares) se implementan para que sean seguros para subprocesos & no sincronizados. El mecanismo no introduce sincronización, pero si se necesita uno, lo maneja con gracia.

4

Si completa su lista y luego la ajusta en el mismo hilo, estará a salvo.

Sin embargo, hay varias cosas a tener en cuenta:

  1. Collections.synchronizedList() sólo le garantiza un hilo de seguridad de bajo nivel. Las operaciones complejas, como if (!list.contains(elem)) list.add(elem);, necesitarán un código de sincronización personalizado.
  2. Incluso esta garantía es nula si cualquier hilo puede obtener una referencia a la lista original. Asegúrate de que esto no suceda.
  3. Primero obtenga la funcionalidad correcta, luego puede comenzar a preocuparse por la lentitud de la sincronización. Raramente encontré código en el que la velocidad de sincronización de Java era un factor serio.

Actualización: Me gustaría añadir algunos extractos de la JLS para aclarar cuestiones de esperar un poco.

Si xey son acciones del mismo hilo yx viene antes que y en el orden del programa, entonces hb (x, y).

Es por eso que llenar la lista y luego envolverla en el mismo hilo es una opción segura. Pero lo más importante:

Esta es una garantía extremadamente sólida para los programadores. Los programadores no necesitan razonar sobre los cambios de orden para determinar si su código contiene razas de datos. Por lo tanto, no necesitan razonar sobre los cambios de orden para determinar si su código está sincronizado correctamente. Una vez que se hace la determinación de que el código está sincronizado correctamente, el programador no tiene que preocuparse de que las modificaciones afecten su código.

El mensaje es claro: asegúrese de que su programa, ejecutado en el orden en el que escribió su código, no contenga razas de datos, y no se preocupe por el reordenamiento.

+0

¿Puedes probar esta afirmación? Tenga en cuenta que la creación de la lista sincronizada no está sincronizada. – axtavt

+0

@axtavt Tenga en cuenta mis palabras de apertura: "SI completa su lista y luego la envuelve en el mismo hilo". No hay nada que probar realmente. – biziclop

+0

¿Cómo se sabe que synchronizedList() garantiza que sucede antes de la relación? Mi suposición es que simple recorriendo la lista sería suficiente para convencer al optimizador de JVM de completar todas las operaciones pendientes de su estructura, pero no estoy seguro ... – Andrey

2

Si los cruces ocurren más a menudo que escribiendo me gustaría ver en CopyOnWriteArrayList.

Una variante thread-safe de ArrayList en que todas las operaciones mutativas (ADD, conjunto, y así sucesivamente) se implementan por hacer una copia fresca de la matriz subyacente.

+0

Esta solución es un poco "grasa". No puede ser rápido con grandes listas. – Andrey

+1

@Andrey - Solo viable si tiene más lecturas que escrituras ... – willcodejavaforfood

Cuestiones relacionadas