En secciones críticas de Java, ¿en qué debería sincronizar?

En Java, la forma idiomática de declarar secciones críticas en el código es la siguiente:

private void doSomething() { // thread-safe code synchronized(this) { // thread-unsafe code } // thread-safe code } 

Casi todos los bloques se sincronizan en this , pero ¿hay alguna razón particular para esto? ¿Hay otras posibilidades? ¿Hay alguna mejor práctica en qué objeto sincronizar? (como instancias privadas de Object ?)

En primer lugar, tenga en cuenta que los siguientes fragmentos de código son idénticos.

 public void foo() { synchronized (this) { // do something thread-safe } } 

y:

 public synchronized void foo() { // do something thread-safe } 

haz exactamente lo mismo No hay preferencia por ninguno de ellos, excepto por la legibilidad y el estilo del código.

Cuando sincroniza métodos o bloques de código, es importante saber por qué lo hace y qué objeto está bloqueando exactamente, y con qué propósito .

También tenga en cuenta que hay situaciones en las que querrá sincronizar bloques de código del lado del cliente en el que el monitor que está solicitando (es decir, el objeto sincronizado) no es necesariamente this , como en este ejemplo:

 Vector v = getSomeGlobalVector(); synchronized (v) { // some thread-safe operation on the vector } 

Sugiero que obtengas más conocimiento sobre la progtwigción concurrente, te servirá mucho una vez que sepas exactamente lo que está sucediendo detrás de escena. Debería consultar la Progtwigción concurrente en Java , un excelente libro sobre el tema. Si desea sumergirse rápidamente en el tema, consulte Java Concurrency @ Sun

Como notaron los contestadores anteriores, es una buena práctica sincronizarse en un objeto de scope limitado (en otras palabras, elija el ámbito más restrictivo con el que pueda salirse, y úselo). En particular, sincronizar con this es una mala idea, a menos que tenga la intención de permitir que los usuarios de su clase ganen el candado.

Sin embargo, se presenta un caso particularmente desagradable si elige sincronizar en un java.lang.String . Las cadenas pueden ser (y en la práctica casi siempre son) internas. Eso significa que cada cadena de contenido igual – en la JVM COMPLETA – resulta ser la misma cadena detrás de las escenas. Eso significa que si sincroniza en cualquier String, otra sección de código (completamente dispar) que también se bloquea en una cadena con el mismo contenido, también bloqueará su código.

Una vez resolví un punto muerto en un sistema de producción y (muy dolorosamente) rastreé el punto muerto a dos paquetes de código abierto completamente dispares que se sincronizaron en una instancia de String cuyos contenidos eran "LOCK" .

Intento evitar sincronizar con this porque eso permitiría a todos los que desde el exterior que tenían una referencia a ese objeto bloquear mi sincronización. En cambio, creo un objeto de sincronización local:

 public class Foo { private final Object syncObject = new Object(); … } 

Ahora puedo usar ese objeto para la sincronización sin temor a que nadie “robe” la cerradura.

Solo para resaltar que también hay ReadWriteLocks disponibles en Java, que se encuentran como java.util.concurrent.locks.ReadWriteLock.

En la mayor parte de mi uso, distingo mi locking como ‘para leer’ y ‘para actualizaciones’. Si simplemente usa una palabra clave sincronizada, todas las lecturas al mismo bloque de método / código estarán ‘en cola’. Solo un hilo puede acceder al bloque a la vez.

En la mayoría de los casos, nunca tendrá que preocuparse por problemas de simultaneidad si solo está leyendo. Es cuando estás escribiendo que te preocupan las actualizaciones concurrentes (lo que resulta en la pérdida de datos), o la lectura durante una escritura (actualizaciones parciales), de la que tienes que preocuparte.

Por lo tanto, un locking de lectura / escritura tiene más sentido para mí durante la progtwigción de subprocesos múltiples.

Querrá sincronizar en un objeto que pueda servir como Mutex. Si la instancia actual ( esta referencia) es adecuada (no un Singleton, por ejemplo), puede usarla, ya que en Java cualquier Objeto puede servir como Mutex.

En otras ocasiones, es posible que desee compartir un Mutex entre varias clases, si las instancias de estas clases pueden necesitar acceso a los mismos recursos.

Depende mucho del entorno en el que esté trabajando y del tipo de sistema que esté creando. En la mayoría de las aplicaciones Java EE que he visto, en realidad no existe una necesidad real de sincronización …

Personalmente, creo que las respuestas que insisten en que nunca o solo rara vez es correcto sincronizar this están equivocadas. Creo que depende de tu API. Si su clase es una implementación segura para hilos y la documenta, entonces debe usar this . Si la sincronización no consiste en hacer que cada instancia de la clase sea totalmente segura en la invocación de sus métodos públicos, entonces debe usar un objeto interno privado. Los componentes reutilizables de la biblioteca a menudo entran en la categoría anterior: debe pensar detenidamente antes de no permitir que el usuario ajuste su API en la sincronización externa.

En el primer caso, usar this permite invocar múltiples métodos de forma atómica. Un ejemplo es PrintWriter, donde es posible que desee generar varias líneas (por ejemplo, un seguimiento de stack en la consola / registrador) y garantizar que aparezcan juntas; en este caso, el hecho de que oculta el objeto de sincronización internamente es una verdadera molestia. Otro ejemplo son los contenedores de recolección sincronizados: allí debe sincronizar en el objeto de colección para iterar; dado que la iteración consiste en múltiples invocaciones de métodos, no se puede proteger totalmente internamente.

En este último caso, utilizo un objeto simple:

 private Object mutex=new Object(); 

Sin embargo, habiendo visto muchos volcados JVM y rastros de stack que dicen que un locking es “una instancia de java.lang.Object ()”, tengo que decir que usar una clase interna a menudo puede ser más útil, como otros han sugerido.

De todos modos, esos son mis dos bits vale la pena.

Editar: Otra cosa, al sincronizar con this , prefiero sincronizar los métodos y mantener los métodos muy detallados. Creo que es más claro y conciso.

La sincronización en Java a menudo implica la sincronización de operaciones en la misma instancia. Sincronizar en this es muy idiomático ya que es una referencia compartida que está disponible automáticamente entre diferentes métodos de instancia (o secciones de) en una clase.

El uso de otra referencia específicamente para el locking, al declarar e inicializar un campo privado Object lock = new Object() por ejemplo, es algo que nunca necesité ni usé. Creo que solo es útil cuando necesita sincronización externa en dos o más recursos no sincronizados dentro de un objeto, aunque siempre intentaría refactorizar dicha situación en una forma más simple.

De todos modos, implícito (método sincronizado) o explícitamente synchronized(this) se usa mucho, también en las bibliotecas de Java. Es un buen idioma y, si corresponde, siempre debe ser su primera opción.

En lo que sincronice depende de qué otros hilos que potencialmente entren en conflicto con este método, la llamada puede sincronizarse.

Si this es un objeto que solo utiliza un hilo y estamos accediendo a un objeto mutable que se comparte entre hilos, un buen candidato es sincronizar sobre ese objeto; la sincronización en this no tiene sentido ya que otro hilo que modifica ese objeto compartido podría Ni siquiera lo sé, pero sí conoce ese objeto.

Por otro lado, la sincronización sobre this tiene sentido si muchos hilos llaman a métodos de este objeto al mismo tiempo, por ejemplo, si estamos en un singleton.

Tenga en cuenta que un método sincronizado a menudo no es la mejor opción, ya que mantenemos un locking todo el tiempo que se ejecuta el método. Si contiene partes que consumen mucho tiempo pero son seguras para el hilo, y una parte insegura del hilo que no consume tanto tiempo, la sincronización con el método es muy incorrecta.

Casi todos los bloques se sincronizan en esto, pero ¿hay alguna razón particular para esto? ¿Hay otras posibilidades?

Esta statement sincroniza todo el método.

 private synchronized void doSomething() { 

Esta statement sincronizó una parte del bloque de código en lugar de un método completo.

 private void doSomething() { // thread-safe code synchronized(this) { // thread-unsafe code } // thread-safe code } 

De la página de documentación de Oracle

hacer estos métodos sincronizados tiene dos efectos:

En primer lugar, no es posible intercalar dos invocaciones de métodos sincronizados en el mismo objeto . Cuando un hilo está ejecutando un método sincronizado para un objeto, todos los otros hilos que invocan métodos sincronizados para el mismo bloque de objetos (suspenden la ejecución) hasta que el primer hilo termina con el objeto.

¿Hay otras posibilidades? ¿Hay alguna mejor práctica en qué objeto sincronizar? (como instancias privadas de Object?)

Hay muchas posibilidades y alternativas a la sincronización. Puede hacer que su código sea seguro utilizando API de concurrencia de alto nivel (disponible desde la versión JDK 1.5)

 Lock objects Executors Concurrent collections Atomic variables ThreadLocalRandom 

Consulte las preguntas a continuación SE para obtener más detalles:

Sincronización vs locking

Evitar sincronizado (esto) en Java?