2012-03-08 15 views
14

Digamos que tenemos una clase Java muy simple MyClass.Clase segura para subprocesos en Java mediante bloques sincronizados

public class MyClass { 
    private int number; 

    public MyClass(int number) { 
     this.number = number; 
    } 

    public int getNumber() { 
     return number; 
    } 

    public void setNumber(int number) { 
     this.number = number; 
    } 
} 

Hay tres maneras de construir la clase de Java flujos seguros que tiene un cierto estado:

  1. hacerla verdaderamente inmutable

    public class MyClass { 
        private final int number; 
    
        public MyClass(int number) { 
        this.number = number; 
        } 
    
        public int getNumber() { 
        return number; 
        } 
    
    } 
    
  2. campo Definir numbervolatile.

    public class MyClass { 
        private volatile int number; 
    
        public MyClass(int number) { 
        this.number = number; 
        } 
    
        public int getNumber() { 
         return number; 
        } 
    
        public void setNumber(int number) { 
         this.number = number; 
        } 
    } 
    
  3. utilizar un bloque synchronized. La versión clásica de este enfoque se describe en el Capítulo 4.3.5 de la concurrencia de Java en la práctica. Y lo curioso es que tiene un error en el ejemplo que se menciona en una errata para este libro.

    public class MyClass { 
        private int number; 
    
        public MyClass(int number) { 
         setNumber(number); 
        } 
    
        public synchronized int getNumber() { 
         return number; 
        } 
    
        public synchronized void setNumber(int number) { 
         this.number = number; 
        } 
    } 
    

Hay un hecho más que debe añadirse al contexto de la discusión. En un entorno multiproveedor, JVM puede reordenar las instrucciones fuera del bloque synchronized conservando una secuencia lógica y ocurre antes de las relaciones especificadas por JVM. Puede causar la publicación de objetos que no están construidos correctamente para otro hilo.

Tengo un par de preguntas sobre el tercer caso.

  1. ¿Será equivalente a una siguiente fragmento de código:

    public class MyClass { 
        private int number; 
    
        public MyClass(int number) { 
         synchronized (this){ 
          this.number = number; 
         } 
        } 
    
        public synchronized int getNumber() { 
         return number; 
        } 
    
        public synchronized void setNumber(int number) { 
         this.number = number; 
        } 
    } 
    
  2. se impedirá que un reordenamiento en el tercer caso o posible que los JVM para reordenar intstructions y, por tanto, publicar objeto con valor por defecto en el campo number?

  3. Si la respuesta a la segunda pregunta es sí, tengo una pregunta más.

    public class MyClass { 
        private int number; 
    
        public MyClass(int number) { 
         synchronized (new Object()){ 
          this.number = number; 
         } 
        } 
    
        public synchronized int getNumber() { 
         return number; 
        } 
    
        public synchronized void setNumber(int number) { 
         this.number = number; 
        } 
    } 
    

Este aspecto extraño synchronized (new Object()) se supone que previene el efecto de reordenamiento. ¿Funcionará?

Para que quede claro, todos estos ejemplos no tienen ninguna aplicación práctica. Solo tengo curiosidad sobre los matices del multihilo.

Respuesta

7

synchronized(new Object()) no hará nada, ya que la sincronización solo se aplica al objeto que sincroniza. Por lo tanto, si el hilo A se sincroniza en oneObject, y el hilo B se sincroniza en anotherObject, no hay pasadas entre ellos. Dado que podemos saber con certeza que ningún otro hilo se sincronizará en el new Object() que crees allí, esto no establecerá un happening previo entre cualquier otro hilo.

En cuanto a su synchronzied en el constructor, si su objeto se publica de forma segura en otro hilo, no lo necesita; y si no es así, probablemente estés en un lío de problemas como es.Hice esta pregunta en la lista de interés de concurrencia hace un momento, y an interesting thread resulted. Ver en particular this email, que señala que incluso con su constructor sincronizado, a falta de publicación segura, otro hilo podría ver los valores predeterminados en sus campos, y this email que (lo une) todo el conjunto.

+2

Hace algo (Fuerza todo en el bloque para completar antes de cualquier cosa que ocurra después en el hilo de construcción, por lo que si el hilo de construcción pasa inmediatamente la referencia a otro hilo después de que el constructor retorne, no necesita preocuparse por los campos que no son finales.) Simplemente no hace * suficiente * para cubrir * todos * los riesgos de concurrencia. – Affe

+1

@Affe Dentro de un hilo, ya está garantizado que verá las cosas en el orden en que el código las dice, sin necesidad de sincronización. Pero no se requiere otro hilo para ver esos efectos en el mismo orden, debido a las carreras de datos. En otras palabras, si el hilo A hace 'A, B, C' (donde, por ejemplo,' A' es la sincronización, 'B' es la asignación de campo,' C' establece la referencia después de 'new'), hilo B se permite ver 'CAB' si no ocurre nada, antes del borde, que' synchronized (new Object()) 'nunca proporcionará. Por ejemplo, si el hilo B está en un núcleo diferente, 'C 'podría enjuagarse antes de' A'. – yshavit

+2

sincronizando dentro del primer subproceso alrededor de la asignación B lo fuerza a ser visible antes de establecer la referencia C. Los efectos secundarios de consistencia de memoria del bloque sincronizado no están acoplados a los efectos secundarios de exclusión. Todavía se aplican para las operaciones realizadas en el primer hilo, ya sea que el otro hilo compita o no para el monitor de objetos. – Affe

2

En la pregunta n. ° 3, synchronized(new Object()) no es necesario y no prevendrá nada. El compilador puede determinar que ningún otro subproceso podría sincronizarse en ese objeto (ya que nada más puede acceder al objeto). Este es un ejemplo explícito en el documento de Brian Goetz "Java theory and practice: Synchronization optimizations in Mustang".

Incluso si tenía necesidad de sincronizar en un constructor, e incluso si su synchronized(new Object()) bloque era útil - es decir, que estaba realizando la sincronización en un objeto de larga duración diferente, ya que sus otros métodos se sincronizan en this, usted tiene problemas de visibilidad si no está sincronizando en la misma variable. Es decir, realmente quiere que su constructor también use synchronized(this).

Un aparte:

Sincronización en la this se considera la falta de forma. En cambio, sincronízate en algún campo final privado. Las personas que llaman pueden sincronizarse en su objeto, lo que podría llevar a un punto muerto. Considere lo siguiente:

public class Foo 
{ 
    private int value; 
    public synchronized int getValue() { return value; } 
    public synchronized void setValue(int value) { this.value = value; } 
} 

public class Bar 
{ 
    public static void deadlock() 
    { 
     final Foo foo = new Foo(); 
     synchronized(foo) 
     { 
      Thread t = new Thread() { public void run() { foo.setValue(1); } }; 
      t.start(); 
      t.join(); 
     } 
    } 
} 

No es obvio para las personas que llaman de la clase Foo que esto sería un punto muerto. Lo mejor es mantener su semántica de bloqueo interna y privada para su clase.

+0

Re punto 1, puede publicar de manera insegura un objeto sin filtrar 'this' del constructor. Por ejemplo, podría asignarlo a un campo estático no 'volátil '. En ese caso, otro hilo estaría dentro de su derecho para ver la referencia pero no las escrituras en sus campos. – yshavit

+0

@yshavit: estuvo de acuerdo (aunque consideraría ese caso "filtrando" esto "" si te estoy leyendo correctamente). Goetz tenía otro artículo en la * teoría y práctica * Java sobre técnicas de construcción seguras a las que agregaré un enlace. –

+2

Estoy de acuerdo con @yshavit - [Especificación del lenguaje Java 17.5] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5) específicamente dice (y muestra en un ejemplo) que los campos no finales pueden necesitar sincronización.Más específicamente: _ Se considera que un objeto está completamente inicializado cuando su constructor termina. Se garantiza que un hilo que solo puede ver una referencia a un objeto después de que ese objeto se haya inicializado completamente vea los valores inicializados correctamente para los campos finales de ese objeto. "_. Tenga en cuenta la penúltima palabra: no existen tales garantías para campos no finales. –

Cuestiones relacionadas