Pregunta

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 en particular para esto? ¿Hay otras posibilidades? ¿Existen prácticas recomendadas sobre qué objeto sincronizar? (como instancias privadas de Object ?)

¿Fue útil?

Solución

Primero, 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é está haciendo tal cosa y qué objeto está bloqueando exactamente, y para < fuerte> qué propósito .

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

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

Le sugiero que obtenga más conocimientos sobre la programación concurrente, le servirá mucho una vez que sepa exactamente lo que está sucediendo detrás de escena. Debería consultar Programación concurrente en Java , un gran libro sobre el tema. Si desea una inmersión rápida en el tema, consulte Java Concurrency @ Sun

Otros consejos

Como han señalado las personas que respondieron anteriormente, es una buena práctica sincronizar un objeto de alcance limitado (en otras palabras, elija el alcance más restrictivo con el que pueda salirse, y úselo). En particular, sincronizando en esto es una mala idea, a menos que tenga la intención de permitir que los usuarios de su clase obtengan el bloqueo.

Sin embargo, surge un caso particularmente feo si elige sincronizar en un java.lang.String . Las cadenas pueden ser (y en la práctica casi siempre son) internados. Eso significa que cada cadena de igual contenido, en la ENTRE JVM , resulta ser la misma cadena detrás de escena. Eso significa que si sincroniza en cualquier Cadena, otra sección de código (completamente diferente) que también se bloquea en una Cadena con el mismo contenido, en realidad también bloqueará su código.

Una vez estuve solucionando 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 cada uno sincronizó en una instancia de String cuyo contenido era " LOCK " .

Intento evitar la sincronización en this porque eso permitiría que todas las personas externas que tenían una referencia a ese objeto bloqueen 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 & # 8220; robe & # 8221; 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, separo mi bloqueo como 'para leer' y 'para actualizaciones'. Si simplemente usa una palabra clave sincronizada, todas las lecturas del mismo método / bloque de código se 'colocará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 concurrencia si simplemente está leyendo. Es cuando tienes que escribir que te preocupas por las actualizaciones concurrentes (que resultan en la pérdida de datos) o leer durante una escritura (actualizaciones parciales), de lo que tienes que preocuparte.

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

Querrá sincronizar en un objeto que puede servir como Mutex. Si la instancia actual (la referencia esta ) es adecuada (no es 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 es posible que todas las instancias de estas clases necesiten acceso a los mismos recursos.

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

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

En el primer caso, el uso de este 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 la pila en la consola / registrador) y garantizar que aparezcan juntas: en este caso, el hecho de que oculte el objeto de sincronización internamente es un verdadero problema. Otro ejemplo de ello son las envolturas de recopilación sincronizadas, donde debe sincronizarse en el objeto de la colección para iterar; ya que la iteración consiste en múltiples invocaciones de métodos que no puede proteger por completo internamente.

En este último caso, uso un objeto simple:

private Object mutex=new Object();

Sin embargo, después de haber visto muchos volcados JVM y seguimientos de pila que dicen que un bloqueo es " una instancia de java.lang.Object () " Tengo que decir que usar una clase interna a menudo puede ser más útil, como han sugerido otros.

De todos modos, ese es el valor de mis dos bits.

Editar: otra cosa, cuando sincronizo en this prefiero sincronizar los métodos y mantener los métodos muy granulares. Creo que es más claro y conciso.

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

Usar otra referencia específicamente para bloquear, al declarar e inicializar un campo privado Object lock = new Object () , por ejemplo, es algo que nunca necesité o 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 trataría de refactorizar tal situación de una forma más simple.

De todos modos,

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

De lo que sincronice depende de qué otros subprocesos que puedan entrar en conflicto con este método se puedan sincronizar.

Si este es un objeto que solo utiliza un hilo y accedemos a un objeto mutable que se comparte entre hilos, un buen candidato es sincronizar sobre ese objeto, sincronizando en esto no tiene sentido ya que otro hilo que modifica ese objeto compartido puede que ni siquiera sepa esto , 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 bloqueo todo el tiempo que se ejecuta el método. Si contiene partes que consumen mucho tiempo pero son seguras para subprocesos, y una parte no segura para subprocesos que no requiere tanto tiempo, la sincronización sobre el método es muy incorrecta.

  

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

Esta declaración sincroniza todo el método.

private synchronized void doSomething() {

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

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

De la documentación de Oracle página

sincronizar estos métodos tiene dos efectos:

Primero, no es posible que dos invocaciones de métodos sincronizados en el mismo objeto se intercalen . Cuando un subproceso está ejecutando un método sincronizado para un objeto, todos los demás subprocesos que invocan métodos sincronizados para el mismo bloque de objeto (suspensión de ejecución) hasta que el primer subproceso se realiza con el objeto.

  

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

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

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

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

Sincronización vs bloqueo

¿Evita sincronizar (esto) en Java?

las Mejores Prácticas es crear un objeto únicamente para proporcionar el bloqueo:

private final Object lock = new Object();

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

Al hacer esto, está seguro de que ningún código de llamada puede bloquear su método mediante una línea involuntaria sincronizada (su Objeto) .

( Créditos a @jared y @ yuval-adam que explicaron esto en más detalles anteriormente. )

Supongo que la popularidad del uso de this en los tutoriales provino de principios de Sun javadoc: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

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