2009-06-11 18 views
10

En mi proyecto de Java, casi todos los métodos no estáticos que he escrito son synchronized. He decidido corregir algunos códigos hoy, eliminando la mayoría de las palabras clave synchronized. Allí mismo creé varios problemas de enhebrado que tardaron bastante en solucionarse, sin aumentar el rendimiento. Al final revertí todo.¿Alguna razón para NO dar una bofetada a la palabra clave "sincronizada" en todas partes?

No veo a nadie escribiendo código con "synchronized" en todas partes. ¿Hay alguna razón por la que no debería tener tener "synchronized" en todas partes?

¿Qué pasa si no me preocupa demasiado el rendimiento (es decir, el método no se llama más de una vez cada pocos segundos)?

+13

La misma razón por la que no hacen planos con material de caja negra ... –

Respuesta

26

Por supuesto, rendimiento. Los monitores tienen un costo.

La respuesta no es eliminar ni agregar la sincronización de forma aleatoria. Es mejor leer algo como "Java Concurrency In Practice" de Brian Goetz o los "hilos de Java" de Doug Lea para entender cómo hacerlo correctamente. Y aprende bien los nuevos paquetes concurrentes, por supuesto.

El subprocesamiento múltiple es mucho más que la palabra clave sincronizada.

+4

Hay preocupaciones mucho más grandes que solo el costo de la sincronización. – Eddie

+11

Preocupaciones más grandes, como las personas que agregan la palabra clave sincronizada a los métodos sin entender realmente por qué o qué hace. Me preocuparía por eso. – izb

+4

Plus: La sobredosis incorrecta de sincronización puede causar cerraduras inactivas – Dirk

2

Solo que con certeza ralentizará las cosas.

Cada objeto sincronizado debe realizar una comprobación de algún tipo para asegurarse de que no esté en uso y luego establecer indicadores cuando se utilizan y reiniciarlos cuando haya terminado.

Esto lleva tiempo. Probablemente no notarás un cambio funcional pero, si el rendimiento es importante, debes tener cuidado.

7

El punto de rendimiento ya se ha indicado.

Además, recuerde que todos los hilos obtienen una copia diferente de la pila. Por lo tanto, si un método solo utiliza variables que se crean dentro de ese método y no hay acceso al mundo externo (por ejemplo, identificadores de archivos, sockets, conexiones de bases de datos, etc.), entonces no hay posibilidad de que se produzcan problemas.

+0

Iba a decir exactamente eso. –

36

Si sincroniza indiscriminadamente, también corre el riesgo de crear un deadlock.

Supongamos que tengo dos clases, Foo y Bar, ambas con un método sincronizado doSomething(). Supongamos además que cada clase tiene un método sincronizado que toma una instancia de la otra clase como parámetro.

public class Foo { 

    synchronized void doSomething() { 
     //code to doSomething 
    } 

    synchronized void doSomethingWithBar(Bar b) { 
     b.doSomething(); 
    } 

} 

public class Bar { 

    synchronized void doSomething() { 
     //code to doSomething 
    } 

    synchronized void doSomethingWithFoo(Foo f) { 
     f.doSomething(); 
    } 
} 

Se puede ver que si tiene una instancia de Foo y una instancia de Bar, tanto intentar ejecutar sus doSomethingWith* métodos en sí, al mismo tiempo, se puede producir un callejón sin salida.

Para forzar el punto muerto, podría insertar un sueño, tanto en el doSomethingWith* métodos (utilizando Foo como el ejemplo):

synchronized void doSomethingWithBar(Bar b) { 
    try { 
     Thread.sleep(10000); 
    } catch (InterruptedException ie) { 
     ie.printStackTrace(); 
    } 

    b.doSomething(); 
} 

En el método principal, se inicia dos hilos para completar el ejemplo:

public static void main(String[] args) { 
    final Foo f = new Foo(); 
    final Bar b = new Bar(); 
    new Thread(new Runnable() { 
     public void run() { 
      f.doSomethingWithBar(b); 
     } 
    }).start(); 

    new Thread(new Runnable() { 
     public void run() { 
      b.doSomethingWithFoo(f); 
     } 
    }).start(); 
} 
+4

Incluso sin interbloqueos todavía puede meterse en un gran problema. Acabo de recibir una operación de E/S dentro de un bloque sincronizado que se llama miles de veces a la semana y demora unos 20 milisegundos. No hay problema. Pero luego, por alguna extraña razón, un par de veces a la semana demora 30 minutos. ¡Ay! –

12

Si piensa que poner la palabra clave "sincronizada" en todas partes es una buena solución, incluso ignorando el rendimiento, entonces no comprende exactamente lo que está sucediendo y no debería usarlo. Recomiendo leer sobre el tema antes de sumergirse sin dirección.

Enhebrar es un tema increíblemente difícil de dominar y es realmente importante entender por qué estás haciendo cosas. De lo contrario, obtendrás un montón de código que funciona por accidente en lugar de por diseño. Esto solo terminará causando dolor.

+6

o mejor aún, ¡no use hilos a menos que sea absolutamente necesario! –

6

Mucho mejor que poner synchronized en todas partes es razonar cuidadosamente sobre las invariantes que sus clases necesitan, luego sincronizar lo suficiente para garantizar esas invariantes. Si el exceso de sincronizar, se crea dos riesgos:

  1. punto muerto
  2. problemas LIVENESS

punto muerto todo el mundo sabe. Los problemas de Liveness no están relacionados con el costo de la sincronización, sino con el hecho de que si sincronizas globalmente todo en una aplicación multiproceso, entonces muchos hilos serán bloqueados esperando obtener un monitor porque otro hilo está tocando algo no relacionado pero usando el mismo monitor.

Si quiere darle una bofetada a una palabra clave en todas partes por seguridad, entonces le recomiendo usar final en lugar de synchronized. :) Cualquier cosa que pueda hacer final contribuye a mejorar la seguridad de los hilos y a razonar más acerca de qué invariantes necesitan ser mantenidos por los bloqueos. Para dar un ejemplo, digamos que usted tiene esta clase trivial:

public class SimpleClass { 
    private volatile int min, max; 
    private volatile Date startTime; 
    // Assume there are many other class members 

    public SimpleClass(final int min, final int max) { 
     this.min = min; 
     this.max = max; 
    } 
    public void setMin(final int min) { 
     // set min and verify that min <= max 
    } 
    public void setMax(final int max) { 
     // set max and verify that min <= max 
    } 
    public Date getStartTime() { 
     return startTime; 
    } 
} 

Con la clase anterior, al establecer valores mínimos y máximos, es necesario sincronizar. El código anterior está roto. ¿Qué invariante tiene esta clase? Debe asegurarse de que min <= max en todo momento, incluso cuando hay varios hilos llamando al setMin o setMax.

Asumiendo que esto es una gran clase con muchas variables y sincroniza todo, entonces si uno subproceso llama setMin y otro subproceso llama getStartTime, el segundo hilo se bloquea hasta que innecesariamente setMin devoluciones. Si haces esto con una clase con muchos miembros, y suponiendo que solo unos pocos de esos miembros están involucrados en invariantes que deben ser defendidos, sincronizar todo causará muchos problemas de vida de este tipo.

1

Digamos que usted tiene este ejemplo

 

synchronized (lock){ 
    doSomething(); 
} 
 

Si ha bloqueado varios hilos que no se puede interrumpir los mismos. Es mejor utilizar Bloquear objetos en lugar de "sincronizar" porque tiene un mejor control de las interrupciones que pueden ocurrir. Por supuesto, como todos sugirieron que también hay algunos problemas de rendimiento con "sincronizados" y si los usa en exceso puede tener bloqueos, interbloqueos, etc.

2

No debería, y probablemente necesite reconsiderar su diseño. Desde mi experiencia, la sincronización en este nivel simplemente no es escalable. Puede solucionar sus problemas inmediatos, pero no ayudará a reparar la integridad general de la aplicación.

Por ejemplo: incluso si tiene una colección "sincronizada", un hilo puede querer llamar a dos métodos "sincronizados" de una vez, pero no quiere que otro hilo vea el resultado intermedio. Por lo tanto, cualquier persona que llama de una API "sincronizada" de grano fino debe ser consciente de ello, lo que degrada en gran medida la usabilidad (de programador) de esta API.

Para empezar, lo mejor es utilizar subprocesos de trabajo. Aísle las tareas específicas de ejecución larga para que los hilos separados tomen un bloque de entrada inmutable y, cuando termine, coloque en cola el resultado en la cola de mensajes del hilo principal (esto, por supuesto, debe estar sincronizado). El hilo principal puede sondear o esperar explícitamente una señal si no tiene nada más que hacer. Los detalles específicos dependen de los requisitos de rendimiento y el diseño de la aplicación.

A largo plazo, intente comprender las implementaciones del modelo de transacción ACID de las implementaciones de bases de datos o la concurrencia de paso de mensajes, ... por ejemplo, el lenguaje Erlang le dará mucha inspiración. Estos son modelos de sincronización que han demostrado ser escalables. Tome lo mejor de ellos e intente aplicar estos conceptos a la aplicación multiproceso en la que trabaja.

0

Si el rendimiento no es un problema, ¿por qué no usa un hilo y luego puede quitar todo "sincronizado" de forma segura? Si sincroniza todo lo que efectivamente ha logrado, lo único que puede hacer un hilo cualquier cosa, pero de una manera mucho más complicada y propensa a errores.

Pregúntate a ti mismo, ¿por qué estás usando más de un hilo?

Cuestiones relacionadas