¿Deberías sincronizar el método de ejecución? ¿Por qué o por qué no?

Siempre he pensado que sincronizar el método de ejecución en una clase Java que implementa Runnable es redundante. Estoy tratando de descubrir por qué la gente hace esto:

public class ThreadedClass implements Runnable{ //other stuff public synchronized void run(){ while(true) //do some stuff in a thread } } } 

Parece redundante e innecesario ya que están obteniendo el locking del objeto para otro hilo. O más bien, están explicitando que solo un hilo tiene acceso al método run (). Pero dado que es el método de ejecución, ¿no es en sí mismo su propio hilo? Por lo tanto, ¿solo puede acceder a sí mismo y no necesita un mecanismo de locking por separado?

Encontré una sugerencia en línea que al sincronizar el método de ejecución podría crear una cola de hilos de facto, por ejemplo, al hacer esto:

  public void createThreadQueue(){ ThreadedClass a = new ThreadedClass(); new Thread(a, "First one").start(); new Thread(a, "Second one, waiting on the first one").start(); new Thread(a, "Third one, waiting on the other two...").start(); } 

Nunca haría eso personalmente, pero se presta a la pregunta de por qué alguien sincronizaría el método de ejecución. ¿Alguna idea de por qué o por qué no uno debe sincronizar el método de ejecución?

Sincronizar el método run() de Runnable es completamente inútil a menos que desee compartir Runnable entre múltiples hilos y quiera secuenciar la ejecución de esos hilos. Lo cual es básicamente una contradicción en términos.

En teoría, existe otro escenario mucho más complicado en el que es posible que desee sincronizar el método run() , que nuevamente implica compartir Runnable entre varios hilos, pero también utiliza wait() y notify() . Nunca lo he visto en más de 14 años de Java.

Hay 1 ventaja al usar el synchronized void blah() sobre void blah() { synchronized(this) { y el bytecode resultante será 1 byte más corto, ya que la sincronización formará parte de la firma del método en lugar de una operación en sí misma . Esto puede influir en la posibilidad de alinear el método con el comstackdor JIT. Aparte de eso, no hay diferencia.

La mejor opción es usar un objeto interno private final Object lock = new Object() para evitar que alguien bloquee potencialmente su monitor. Logra el mismo resultado sin la desventaja del locking exterior maléfico. Tienes ese byte extra, pero rara vez hace la diferencia.

Entonces diría que no, no use la palabra clave synchronized en la firma. En su lugar, use algo como

 public class ThreadedClass implements Runnable{ private final Object lock = new Object(); public void run(){ synchronized(lock) { while(true) //do some stuff in a thread } } } } 

Edite en respuesta al comentario:

Considere lo que hace la sincronización: evita que otros hilos entren en el mismo bloque de código. Entonces imagina que tienes una clase como la siguiente. Digamos que el tamaño actual es 10. Alguien intenta realizar un agregado y fuerza un cambio de tamaño de la matriz de respaldo. Mientras están en el medio de cambiar el tamaño de la matriz, alguien llama a makeExactSize(5) en un hilo diferente. Ahora, de repente, estás tratando de acceder a los data[6] y te bombardea. Se supone que la sincronización evita que eso suceda. En los progtwigs multiproceso, simplemente necesita la sincronización NECESARIA.

 class Stack { int[] data = new int[10]; int pos = 0; void add(int inc) { if(pos == data.length) { int[] tmp = new int[pos*2]; for(int i = 0; i < pos; i++) tmp[i] = data[i]; data = tmp; } data[pos++] = inc; } int remove() { return data[pos--]; } void makeExactSize(int size) { int[] tmp = new int[size]; for(int i = 0; i < size; i++) tmp[i] = data[i]; data = tmp; } } 

¿Por qué? Mínima seguridad adicional y no veo ningún escenario plausible en el que suponga una diferencia.

Por qué no? No es estándar. Si está codificando como parte de un equipo, cuando otro miembro vea su run sincronizada probablemente perderá 30 minutos tratando de descubrir qué tiene de especial la run o el marco que está utilizando para ejecutar Runnable ‘s. .

Desde mi experiencia, no es útil agregar la palabra clave “sincronizada” al método run (). Si necesitamos sincronizar varios hilos, o necesitamos una cola segura para hilos, podemos usar componentes más apropiados, como ConcurrentLinkedQueue.

Bueno, en teoría, podría llamar al método run sin problemas (después de todo, es público). Pero eso no significa que uno deba hacerlo. Así que, básicamente, no hay ninguna razón para hacer esto, además de agregar una sobrecarga insignificante a la ejecución de ejecución de subprocesos (). Bueno, excepto si usas la instancia varias veces para llamar al new Thread , aunque estoy a) no estoy seguro de que sea legal con el threading API yb) parezca completamente inútil.

Además, tu createThreadQueue no funciona. synchronized en un método no estático se sincroniza en el objeto de instancia (es decir, this ), por lo que los tres hilos se ejecutarán en paralelo.

Revise los comentarios del código y descomente y ejecute los diferentes bloques para ver claramente la diferencia, la sincronización de notas tendrá una diferencia solo si se usa la misma instancia ejecutable, si cada subproceso iniciado obtiene un nuevo ejecutable, no habrá ninguna diferencia.

 class Kat{ public static void main(String... args){ Thread t1; // MyUsualRunnable is usual stuff, only this will allow concurrency MyUsualRunnable m0 = new MyUsualRunnable(); for(int i = 0; i < 5; i++){ t1 = new Thread(m0);//*imp* here all threads created are passed the same runnable instance t1.start(); } // run() method is synchronized , concurrency killed // uncomment below block and run to see the difference MySynchRunnable1 m1 = new MySynchRunnable1(); for(int i = 0; i < 5; i++){ t1 = new Thread(m1);//*imp* here all threads created are passed the same runnable instance, m1 // if new insances of runnable above were created for each loop then synchronizing will have no effect t1.start(); } // run() method has synchronized block which lock on runnable instance , concurrency killed // uncomment below block and run to see the difference /* MySynchRunnable2 m2 = new MySynchRunnable2(); for(int i = 0; i < 5; i++){ // if new insances of runnable above were created for each loop then synchronizing will have no effect t1 = new Thread(m2);//*imp* here all threads created are passed the same runnable instance, m2 t1.start(); }*/ } } class MyUsualRunnable implements Runnable{ @Override public void run(){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } class MySynchRunnable1 implements Runnable{ // this is implicit synchronization //on the runnable instance as the run() // method is synchronized @Override public synchronized void run(){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } class MySynchRunnable2 implements Runnable{ // this is explicit synchronization //on the runnable instance //inside the synchronized block // MySynchRunnable2 is totally equivalent to MySynchRunnable1 // usually we never synchronize on this or synchronize the run() method @Override public void run(){ synchronized(this){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } } 

en realidad es muy fácil justificar “sincronizar o no sincronizar”

si la invocación de su método puede mutar el estado interno de su objeto, entonces “sincronizar” de lo contrario no es necesario

ejemplo simple

 public class Counter { private int count = 0; public void incr() { count++; } public int getCount() { return count; } } 

en el ejemplo anterior, incr () necesita sincronizarse, ya que cambiará el valor del conteo, mientras que la sincronización getCount () no es necesaria

sin embargo, hay otro caso de esquina, si el recuento es java.lang.Long, Double, Object, entonces debe declarar como

 private volatile long count = 0; 

para asegurarse de que la actualización de la referencia sea atómica

Básicamente, eso es lo que necesitas pensar el 99% del tiempo cuando se trata de multihilo