Pregunta

Siempre que surge una pregunta en SO sobre la sincronización de Java, algunas personas están muy ansiosas por señalar que se debe evitar sincronizado (esto) . En su lugar, afirman, es preferible un bloqueo en una referencia privada.

Algunas de las razones dadas son:

Otras personas, incluyéndome a mí, argumentan que sincronizado (esto) es un idioma que se usa mucho (también en bibliotecas Java), es seguro y se entiende bien. No debe evitarse porque tiene un error y no tiene ni idea de lo que está sucediendo en su programa multiproceso. En otras palabras: si es aplicable, úsela.

Estoy interesado en ver algunos ejemplos del mundo real (sin cosas de foobar) donde evitar un bloqueo en this es preferible cuando sincronizado (this) también haría el trabajo .

Por lo tanto: ¿debe evitar siempre sincronizado (esto) y reemplazarlo con un candado en una referencia privada?


Alguna información adicional (actualizada a medida que se dan las respuestas):

  • estamos hablando de sincronización de instancia
  • se consideran tanto implícitos (métodos sincronizados ) como explícitos sincronizados (esto)
  • si cita a Bloch u otras autoridades sobre el tema, no omita las partes que no le gustan (por ejemplo, Java efectivo, elemento sobre Seguridad de subprocesos: Normalmente es el bloqueo de la instancia en sí, pero hay excepciones).
  • si necesita granularidad en su bloqueo que no sea sincronizado (esto) , entonces sincronizado (esto) no es aplicable, por lo que ese no es el problema
¿Fue útil?

Solución

Cubriré cada punto por separado.

  1.   

    Algún código maligno puede robar tu cerradura (muy popular este, también tiene un   " accidentalmente " variante)

    Estoy más preocupado por accidentalmente . Lo que significa es que este uso de this es parte de la interfaz expuesta de su clase, y debe documentarse. A veces se desea la capacidad de otro código para usar su bloqueo. Esto es cierto para cosas como Collections.synchronizedMap (vea el javadoc).

  2.   

    Todos los métodos sincronizados dentro de la misma clase usan exactamente el mismo   bloqueo, lo que reduce el rendimiento

    Este es un pensamiento demasiado simplista; simplemente deshacerse de sincronizado (esto) no resolverá el problema. La sincronización adecuada para el rendimiento requerirá más reflexión.

  3.   

    Estás (innecesariamente) exponiendo demasiada información

    Esta es una variante del n. ° 1. El uso de sincronizado (esto) es parte de su interfaz. Si no quiere / necesita esto expuesto, no lo haga.

Otros consejos

Bueno, en primer lugar debe señalarse que:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

es semánticamente equivalente a:

public synchronized void blah() {
  // do stuff
}

que es una razón para no usar sincronizado (esto) . Podría argumentar que puede hacer cosas alrededor del bloque sincronizado (esto) . La razón habitual es tratar de evitar tener que hacer la verificación sincronizada, lo que lleva a todo tipo de problemas de concurrencia, específicamente el problema de bloqueo de doble verificación , que solo muestra lo difícil que puede ser hacer que una verificación relativamente sencilla sea segura.

Una cerradura privada es un mecanismo defensivo, que nunca es una mala idea.

Además, como aludiste, los bloqueos privados pueden controlar la granularidad. Un conjunto de operaciones en un objeto puede no estar relacionado con otro, pero sincronizado (esto) excluirá mutuamente el acceso a todos ellos.

sincronizado (esto) simplemente no te da nada.

Mientras usa sincronizado (esto), usa la instancia de clase como un bloqueo en sí mismo. Esto significa que mientras hilo 1 adquiere el bloqueo, el hilo 2 debería esperar.

Suponga el siguiente código:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

Método 1 modificando la variable a y método 2 modificando la variable b , la modificación concurrente de la misma variable por dos hilos debería evitarse y así es. PERO mientras thread1 modifica a y thread2 modifica b puede realizarse sin ninguna condición de carrera.

Desafortunadamente, el código anterior no permitirá esto ya que estamos usando la misma referencia para un bloqueo; Esto significa que los subprocesos, incluso si no están en condiciones de carrera, deberían esperar y, obviamente, el código sacrifica la concurrencia del programa.

La solución es utilizar 2 bloqueos diferentes para dos variables diferentes:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

El ejemplo anterior usa más bloqueos de grano fino (2 bloqueos en lugar de uno ( lockA y lockB para las variables a y b respectivamente) y como resultado permite una mejor concurrencia, por otro lado se volvió más complejo que el primer ejemplo ...

Si bien estoy de acuerdo en no adherirme ciegamente a las reglas dogmáticas, ¿el "bloqueo de robo" escenario te parece tan excéntrico? De hecho, un hilo podría adquirir el bloqueo de su objeto '' externamente '' ( sincronizado (theObject) {...} ), bloqueando otros hilos que esperan métodos de instancia sincronizados.

Si no cree en el código malicioso, considere que este código podría provenir de terceros (por ejemplo, si desarrolla algún tipo de servidor de aplicaciones).

El " accidental " la versión parece menos probable, pero como dicen, "haga algo a prueba de idiotas y alguien inventará un mejor idiota".

Así que estoy de acuerdo con la escuela de pensamiento de que depende de lo que la clase haga.


Edite los siguientes 3 comentarios de eljenso:

Nunca he experimentado el problema de robo de bloqueo, pero aquí hay un escenario imaginario:

Digamos que su sistema es un contenedor de servlets, y el objeto que estamos considerando es la implementación ServletContext . Su método getAttribute debe ser seguro para subprocesos, ya que los atributos de contexto son datos compartidos; así que lo declaras como sincronizado . Imaginemos también que proporciona un servicio de alojamiento público basado en la implementación de su contenedor.

Soy su cliente y despliego mi "bien" servlet en tu sitio. Sucede que mi código contiene una llamada a getAttribute .

Un hacker, disfrazado de otro cliente, despliega su servlet malicioso en su sitio. Contiene el siguiente código en el método init :

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

Suponiendo que compartimos el mismo contexto de servlet (permitido por la especificación siempre que los dos servlets estén en el mismo host virtual), mi llamada en getAttribute está bloqueada para siempre. El hacker ha logrado un DoS en mi servlet.

Este ataque no es posible si getAttribute está sincronizado en un bloqueo privado, porque el código de terceros no puede adquirir este bloqueo.

Admito que el ejemplo es artificial y una visión demasiado simplista de cómo funciona un contenedor de servlets, pero en mi humilde opinión lo demuestra.

Por lo tanto, tomaría mi decisión de diseño en función de consideraciones de seguridad: ¿tendré control total sobre el código que tiene acceso a las instancias? ¿Cuál sería la consecuencia de que un hilo mantenga un bloqueo en una instancia indefinidamente?

Parece un consenso diferente en los campos de C # y Java sobre esto. La mayoría del código Java que he visto usa:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

mientras que la mayoría del código C # opta por lo más seguro:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

El idioma de C # es ciertamente más seguro. Como se mencionó anteriormente, no se puede hacer un acceso malicioso / accidental a la cerradura desde fuera de la instancia. El código Java también tiene este riesgo, pero parece que la comunidad Java ha gravitado con el tiempo hacia la versión un poco menos segura, pero un poco más concisa.

Eso no significa una excavación contra Java, solo un reflejo de mi experiencia trabajando en ambos idiomas.

Depende de la situación.
Si solo hay una entidad que comparte o más de una.

Ver ejemplo de trabajo completo aquí

Una pequeña introducción.

Hilos y entidades compartibles
Es posible que múltiples hilos accedan a la misma entidad, por ejemplo, múltiples hilos de conexión que comparten un solo mensajeQueue. Dado que los subprocesos se ejecutan simultáneamente, puede existir la posibilidad de anular los datos de uno por otro, lo que puede ser una situación desordenada. Por lo tanto, necesitamos alguna forma de asegurarnos de que solo se acceda a una entidad que se puede compartir a la vez. (CONCURRENCIA).

Bloque sincronizado
Synchronized () es una forma de garantizar el acceso concurrente de la entidad que se puede compartir.
Primero, una pequeña analogía
Suponga que hay dos personas P1, P2 (hilos), un lavabo (entidad que se puede compartir) dentro de un baño y hay una puerta (cerradura).
Ahora queremos que una persona use el lavabo a la vez.
Un enfoque es cerrar la puerta con P1 cuando la puerta está cerrada P2 espera hasta que p1 complete su trabajo
P1 abre la puerta
entonces solo p1 puede usar el lavabo.

sintaxis.

synchronized(this)
{
  SHARED_ENTITY.....
}

" esto " proporcionó el bloqueo intrínseco asociado con la clase (la clase Object diseñada por el desarrollador Java de tal manera que cada objeto pueda funcionar como monitor). El enfoque anterior funciona bien cuando solo hay una entidad compartida y varios subprocesos (1: N).
   ingrese la descripción de la imagen aquí N entidades compartibles: subprocesos M
Ahora piense en una situación en la que hay dos lavabos dentro de un baño y solo una puerta. Si estamos utilizando el enfoque anterior, solo p1 puede usar un lavabo a la vez, mientras que p2 esperará afuera. Es un desperdicio de recursos ya que nadie está usando B2 (lavabo).
Un enfoque más inteligente sería crear una habitación más pequeña dentro del baño y proporcionarles una puerta por lavabo. De esta manera, P1 puede acceder a B1 y P2 puede acceder a B2 y viceversa.

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

 ingrese la descripción de la imagen aquí
ingrese la descripción de la imagen aquí

Ver más sobre hilos ---- > aquí

El paquete java.util.concurrent ha reducido enormemente la complejidad de mi código seguro para subprocesos. Solo tengo evidencia anecdótica para continuar, pero la mayoría del trabajo que he visto con synchronized (x) parece estar implementando un Lock, Semaphore o Latch, pero usando los monitores de nivel inferior.

Con esto en mente, la sincronización con cualquiera de estos mecanismos es análoga a la sincronización en un objeto interno, en lugar de perder un bloqueo. Esto es beneficioso porque tiene la certeza absoluta de que controla la entrada en el monitor mediante dos o más subprocesos.

  1. Haga que sus datos sean inmutables si es posible ( final variables)
  2. Si no puede evitar la mutación de datos compartidos en varios subprocesos, utilice construcciones de programación de alto nivel [p. ej. Lock API]
  3. granular
  

Un bloqueo proporciona acceso exclusivo a un recurso compartido: solo un hilo a la vez puede adquirir el bloqueo y todo acceso al recurso compartido requiere que el bloqueo se adquiera primero.

Código de muestra para usar ReentrantLock que implementa la interfaz Lock

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Ventajas del bloqueo sobre sincronizado (esto)

  1. El uso de métodos o declaraciones sincronizadas obliga a que todas las adquisiciones y liberaciones de bloqueos ocurran de forma estructurada en bloques.

  2. Las implementaciones de bloqueo proporcionan una funcionalidad adicional sobre el uso de métodos y declaraciones sincronizadas al proporcionar

    1. Un intento sin bloqueo para adquirir un bloqueo ( tryLock () )
    2. Un intento de adquirir el bloqueo que se puede interrumpir ( lockInterruptibly () )
    3. Un intento de adquirir el bloqueo que puede agotar el tiempo de espera ( tryLock (long, TimeUnit) ).
  3. Una clase de bloqueo también puede proporcionar un comportamiento y una semántica que es bastante diferente de la del bloqueo de monitor implícito, como

    1. pedido garantizado
    2. uso no entrante
    3. Detección de punto muerto

Eche un vistazo a esta pregunta SE con respecto a varios tipos de Locks :

Sincronización vs bloqueo

Puede lograr la seguridad de subprocesos utilizando API de concurrencia avanzada en lugar de bloques sincronizados. Esta documentación página proporciona buenas construcciones de programación para lograr la seguridad del hilo.

Bloquear objetos admite modismos de bloqueo que simplifican muchas aplicaciones simultáneas.

Ejecutores define Una API de alto nivel para lanzar y gestionar hilos. Las implementaciones de ejecutor proporcionadas por java.util.concurrent proporcionan una gestión de grupo de subprocesos adecuada para aplicaciones a gran escala.

Colecciones concurrentes facilita la gestión de grandes colecciones de datos y puede reducir en gran medida la necesidad de sincronización.

Variables atómicas tienen características que minimizan la sincronización y ayudan a evitar errores de consistencia de memoria.

ThreadLocalRandom (en JDK 7) proporciona una generación eficiente de números pseudoaleatorios a partir de múltiples hilos.

Consulte java.util .concurrent y java.util.concurrent.atomic también paquetes para otras construcciones de programación.

Si ha decidido eso:

  • lo que debes hacer es bloquear el objeto actual; y
  • quieres bloquearlo con una granularidad menor que un método completo;

entonces no veo el tabú sobre syncizezd (esto).

Algunas personas usan deliberadamente sincronizado (esto) (en lugar de marcar el método sincronizado) dentro de todo el contenido de un método porque piensan que es "más claro para el lector". en qué objeto se está sincronizando realmente. Mientras las personas hagan una elección informada (por ejemplo, comprendan que al hacerlo, en realidad están insertando bytecodes adicionales en el método y esto podría tener un efecto secundario en las optimizaciones potenciales), no veo un problema en particular con esto . Siempre debe documentar el comportamiento concurrente de su programa, por lo que no veo que el "sincronizado" publique el comportamiento "". argumento como tan convincente.

En cuanto a la pregunta de qué bloqueo de objeto debería usar, creo que no hay nada de malo en sincronizar en el objeto actual si esto sería esperado por la lógica de lo que está haciendo y cómo sería normalmente su clase usado . Por ejemplo, con una colección, el objeto que lógicamente esperaría bloquear es generalmente la colección misma.

Creo que hay una buena explicación de por qué cada una de estas son técnicas vitales en tu haber en un libro llamado Java Concurrency In Practice de Brian Goetz. Él deja un punto muy claro: debe usar el mismo candado '' EN TODAS PARTES '' para proteger el estado de su objeto. El método sincronizado y la sincronización en un objeto a menudo van de la mano. P.ej. Vector sincroniza todos sus métodos. Si tiene un controlador para un objeto vectorial y va a hacer "poner si está ausente" entonces, simplemente, Vector sincronizando sus propios métodos individuales no lo protegerá de la corrupción del estado. Necesita sincronizar usando sincronizado (vectorHandle). Esto dará como resultado que el mismo bloqueo sea adquirido por cada subproceso que tenga un identificador para el vector y protegerá el estado general del vector. Esto se llama bloqueo del lado del cliente. De hecho, sabemos que el vector se sincroniza (esto) / sincroniza todos sus métodos y, por lo tanto, la sincronización en el objeto vectorHandle dará como resultado una sincronización adecuada del estado de los objetos vectoriales. Es una tontería creer que eres seguro para subprocesos solo porque estás usando una colección segura para subprocesos. Esta es precisamente la razón por la que ConcurrentHashMap introdujo explícitamente el método putIfAbsent: para hacer que tales operaciones sean atómicas.

En resumen

  1. La sincronización a nivel de método permite el bloqueo del lado del cliente.
  2. Si tiene un objeto de bloqueo privado, hace que el bloqueo del lado del cliente sea imposible. Esto está bien si sabe que su clase no tiene "poner si está ausente" tipo de funcionalidad.
  3. Si está diseñando una biblioteca, entonces sincronizar esto o sincronizar el método suele ser más sabio. Porque rara vez estás en condiciones de decidir cómo se usará tu clase.
  4. Si Vector hubiera utilizado un objeto de bloqueo privado, habría sido imposible ponerlo "ausente si no estuviera" derecho. El código del cliente nunca obtendrá un control del bloqueo privado, lo que infringe la regla fundamental de usar el BLOQUEO MISMO EXACTO para proteger su estado.
  5. Sincronizar con esto o los métodos sincronizados tienen un problema como otros han señalado: alguien podría obtener un bloqueo y nunca liberarlo. Todos los demás subprocesos seguirían esperando que se libere el bloqueo.
  6. Entonces, sepa lo que está haciendo y adopte el correcto.
  7. Alguien argumentó que tener un objeto de bloqueo privado le brinda una mejor granularidad, p. Si dos operaciones no están relacionadas, podrían estar protegidas por diferentes bloqueos, lo que daría como resultado un mejor rendimiento. Pero creo que esto es olor de diseño y no olor de código: si dos operaciones no tienen relación alguna, ¿por qué forman parte de la MISMA clase? ¿Por qué un club de clase no tiene funcionalidades relacionadas? Puede ser una clase de utilidad? Hmmmm: ¿alguna utilidad que proporcione manipulación de cadenas y formato de fecha de calendario a través de la misma instancia? ... al menos no tiene ningún sentido para mí!

No, no deberías siempre . Sin embargo, tiendo a evitarlo cuando hay múltiples preocupaciones sobre un objeto en particular que solo necesitan ser seguras con respecto a ellos mismos. Por ejemplo, puede tener un objeto de datos mutables que tenga " etiqueta " y "padre" campos; estos deben ser seguros para subprocesos, pero cambiar uno no tiene por qué impedir que el otro se escriba / lea. (En la práctica, evitaría esto declarando los campos volátiles y / o usando los envoltorios AtomicFoo de java.util.concurrent).

La sincronización en general es un poco torpe, ya que bloquea un gran bloqueo en lugar de pensar exactamente cómo se podría permitir que los hilos trabajen uno alrededor del otro. Usar sincronizado (esto) es aún más torpe y antisocial, ya que dice "nadie puede cambiar nada en esta clase mientras mantengo el bloqueo". ¿Con qué frecuencia necesitas hacer eso?

Preferiría tener cerraduras más granulares; incluso si desea evitar que todo cambie (tal vez está serializando el objeto), puede adquirir todos los bloqueos para lograr lo mismo, además es más explícito de esa manera. Cuando usa sincronizado (esto) , no está claro exactamente por qué está sincronizando o cuáles son los efectos secundarios. Si usa sincronizado (labelMonitor) , o incluso mejor labelLock.getWriteLock (). Lock () , está claro lo que está haciendo y cuáles son los efectos de su sección crítica limitado a.

Respuesta corta : debe comprender la diferencia y elegir según el código.

Respuesta larga : en general, preferiría tratar de evitar sincronizar (esto) para reducir la contención, pero los bloqueos privados añaden complejidad que debe tener en cuenta. Así que use la sincronización correcta para el trabajo correcto. Si no tiene tanta experiencia con la programación de subprocesos múltiples, prefiero seguir bloqueando instancias y leer sobre este tema. (Dicho esto: el solo uso de sincronizar (esto) no hace que su clase sea completamente segura para subprocesos). Este no es un tema fácil, pero una vez que se acostumbre, la respuesta es si usar < em> sincronizar (esto) o no viene naturalmente.

Un bloqueo se usa para visibilidad o para proteger algunos datos de modificaciones concurrentes que pueden conducir a la carrera.

Cuando solo necesita realizar operaciones de tipo primitivas para que sean atómicas, hay opciones disponibles como AtomicInteger y los me gusta.

Pero supongamos que tiene dos números enteros que están relacionados entre sí, como las coordenadas x y y , que están relacionadas entre sí y deben cambiarse de forma atómica. manera. Entonces los protegerías usando un mismo candado.

Un bloqueo solo debe proteger el estado relacionado entre sí. No menos y no más. Si usa sincronizado (esto) en cada método, incluso si el estado de la clase no está relacionado, todos los subprocesos se enfrentarán incluso si se actualiza el estado no relacionado.

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

En el ejemplo anterior, solo tengo un método que muta tanto x como y y no dos métodos diferentes como x y y están relacionados y si hubiera dado dos métodos diferentes para mutar x y y por separado, entonces no habría sido seguro para subprocesos.

Este ejemplo es solo para demostrar y no necesariamente la forma en que debe implementarse. La mejor manera de hacerlo sería hacerlo IMMUTABLE .

Ahora en oposición al ejemplo de Point , hay un ejemplo de TwoCounters ya proporcionado por @Andreas donde el estado que está siendo protegido por dos bloqueos diferentes como el estado es no relacionados entre sí.

El proceso de usar diferentes bloqueos para proteger estados no relacionados se denomina Bloqueo de rayas o Bloqueo de división

La razón para no sincronizar en esto es que a veces necesita más de un bloqueo (el segundo bloqueo a menudo se elimina después de pensarlo un poco más, pero aún lo necesita en el estado intermedio). Si bloquea esto , siempre debe recordar cuál de los dos bloqueos es esto ; si bloquea un objeto privado, el nombre de la variable le dice eso.

Desde el punto de vista del lector, si ve bloquearse en esto , siempre tiene que responder las dos preguntas:

  1. ¿qué tipo de acceso está protegido por esto ?
  2. es un bloqueo realmente suficiente, ¿alguien no introdujo un error?

Un ejemplo:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

Si dos hilos comienzan con longOperation () en dos instancias diferentes de BadObject , adquieren sus cerraduras cuando es hora de invocar l.onMyEvent (...) , tenemos un punto muerto porque ninguno de los hilos puede adquirir el bloqueo del otro objeto.

En este ejemplo, podemos eliminar el punto muerto utilizando dos bloqueos, uno para operaciones cortas y otro para operaciones largas.

Como ya se dijo aquí, el bloque sincronizado puede usar una variable definida por el usuario como objeto de bloqueo, cuando la función sincronizada usa solo " this " ;. Y, por supuesto, puede manipular las áreas de su función que deberían estar sincronizadas, etc.

Pero todos dicen que no hay diferencia entre la función sincronizada y el bloque que cubre la función completa usando " this " como objeto de bloqueo. Eso no es cierto, la diferencia está en el código de bytes que se generará en ambas situaciones. En caso de uso de bloque sincronizado, debe asignarse una variable local que contenga referencia a " esto " ;. Y como resultado, tendremos un tamaño de función un poco mayor (no relevante si tiene pocas funciones).

Explicación más detallada de la diferencia que puedes encontrar aquí: http://www.artima.com/insidejvm/ed2/threadsynchP.html

Además, el uso del bloque sincronizado no es bueno debido al siguiente punto de vista:

  

La palabra clave sincronizada es muy limitada en un área: al salir de un bloque sincronizado, todos los hilos que esperan ese bloqueo deben desbloquearse, pero solo uno de esos hilos puede tomar el bloqueo; todos los demás ven que se ha bloqueado y vuelven al estado bloqueado. Eso no es solo un montón de ciclos de procesamiento desperdiciados: a menudo, el cambio de contexto para desbloquear un hilo también implica paginar la memoria del disco, y eso es muy, muy costoso.

Para más detalles en esta área, le recomendaría que lea este artículo: http://java.dzone.com/articles/synchronized-considered

Esto es realmente complementario a las otras respuestas, pero si su principal objeción al uso de objetos privados para el bloqueo es que satura su clase con campos que no están relacionados con la lógica comercial, entonces el Proyecto Lombok tiene @Synchronized para generar el repetitivo en tiempo de compilación:

@Synchronized
public int foo() {
    return 0;
}

compila a

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

Un buen ejemplo de uso sincronizado (esto).

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

Como puede ver aquí, usamos sincronizar en esto para cooperar fácilmente de forma prolongada (posiblemente un bucle infinito del método de ejecución) con algunos métodos sincronizados allí.

Por supuesto, se puede reescribir muy fácilmente usando sincronizado en campo privado. Pero a veces, cuando ya tenemos un diseño con métodos sincronizados (es decir, clase heredada, derivamos de, sincronizado (esto) puede ser la única solución).

Depende de la tarea que quieras hacer, pero no la usaría. Además, verifique si el proceso de guardado de subprocesos que desea lograr no se pudo hacer sincronizando (esto) en primer lugar. También hay algunos buenos bloqueos en la API que podría ayudarte :)

Solo quiero mencionar una posible solución para referencias privadas únicas en partes atómicas de código sin dependencias. Puede usar un Hashmap estático con bloqueos y un método estático simple llamado atomic () que crea las referencias requeridas automáticamente utilizando la información de la pila (nombre completo de la clase y número de línea). Luego puede usar este método para sincronizar sentencias sin escribir un nuevo objeto de bloqueo.

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

Evite usar sincronizado (esto) como mecanismo de bloqueo: esto bloquea toda la instancia de clase y puede causar puntos muertos. En tales casos, refactorice el código para bloquear solo un método o variable específicos, de esa manera no se bloqueará toda la clase. Synchronized puede usarse dentro del nivel de método.
En lugar de usar sincronizado (esto) , el siguiente código muestra cómo puede bloquear un método.

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

Mis dos centavos en 2019 a pesar de que esta pregunta ya podría haberse resuelto.

Bloquear "esto" no es malo si sabes lo que estás haciendo, pero detrás de la escena bloqueando "esto" es (que desafortunadamente lo que permite la palabra clave sincronizada en la definición del método).

Si realmente desea que los usuarios de su clase puedan 'robar' su bloqueo (es decir, evitar que otros hilos lo traten), realmente desea que todos los métodos sincronizados esperen mientras se está ejecutando otro método de sincronización, etc. Debe ser intencional y bien pensado (y, por lo tanto, documentado para ayudar a los usuarios a comprenderlo).

Para más detalles, en el reverso debe saber lo que está 'ganando' (o 'perdiendo') si bloquea una cerradura no accesible (nadie puede 'robar' su cerradura, usted tiene el control total y pronto...).

El problema para mí es que la palabra clave sincronizada en la firma de definición de método hace que sea demasiado fácil para los programadores no pensar sobre qué bloquear, lo cual es algo muy importante en lo que pensar si no No quiero tener problemas en un programa multiproceso.

No se puede argumentar que "típicamente" no desea que los usuarios de su clase puedan hacer estas cosas o que "típicamente" desee ... Depende de qué funcionalidad esté codificando. No puede hacer una regla general ya que no puede predecir todos los casos de uso.

Considerar por ej. el impresor que usa un bloqueo interno pero luego las personas luchan por usarlo desde múltiples hilos si no quieren que su salida se entrelace.

En caso de que su cerradura sea accesible fuera de la clase o no, es su decisión como programador en función de la funcionalidad que tenga la clase. Es parte de la api. No puede alejarse, por ejemplo, de sincronizado (esto) a sincronizado (provateObjet) sin arriesgarse a romper los cambios en el código que lo usa.

Nota 1: Sé que puede lograr lo que sincroniza (esto) 'logra' mediante el uso de un objeto de bloqueo explícito y exponiéndolo, pero creo que es innecesario si su comportamiento está bien documentado y realmente sabe qué bloquear en 'esto' significa.

Nota 2: No estoy de acuerdo con el argumento de que si algún código está robando accidentalmente su bloqueo, es un error y debe resolverlo. De alguna manera, esto es el mismo argumento que decir que puedo hacer públicos todos mis métodos, incluso si no están destinados a ser públicos. Si alguien llama "accidentalmente" a mi método destinado a ser privado, es un error. ¿Por qué permitir este accidente en primer lugar! Si la capacidad de robar su cerradura es un problema para su clase, no lo permita. Tan simple como eso.

Creo que los puntos uno (otra persona que usa su candado) y dos (todos los métodos que usan el mismo candado innecesariamente) pueden suceder en cualquier aplicación bastante grande. Especialmente cuando no hay una buena comunicación entre los desarrolladores.

No está escrito en piedra, es sobre todo una cuestión de buenas prácticas y prevención de errores.

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