java - seguidores - ¿Debería sincronizar el método de ejecución? ¿Por qué o por qué no?
hashtag para instagram copiar y pegar (7)
Siempre he pensado que la sincronización del método de ejecución en una clase java que implementa Runnable es redundante. Estoy tratando de averiguar 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 bloqueo 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 bloqueo por separado?
Encontré una sugerencia en línea de que al sincronizar el método de ejecución, podría crear una cola de subprocesos de facto, por ejemplo, haciendo 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?
¿Por qué? Seguridad extra mínima y no veo ningún escenario plausible en el que haría una diferencia.
Por qué no? No es estándar. Si está codificando como parte de un equipo, cuando algún otro miembro vea su run
sincronizada, probablemente perderá 30 minutos tratando de descubrir qué es tan especial con su run
o con el marco que está usando para ejecutar el Runnable
''s .
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. Básicamente, no hay razón para hacer esto, aparte de agregar una sobrecarga insignificante al subproceso que llama a run (). 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.
También su 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 subprocesos se ejecutarán en paralelo.
Desde mi experiencia, no es útil agregar una 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.
Hay una ventaja de usar synchronized void blah()
sobre void blah() { synchronized(this) {
y ese es su bytecode resultante será 1 byte más corto, ya que la sincronización será parte de la firma del método en lugar de una operación por sí misma . Esto puede influir en la posibilidad de alinear el método con el compilador JIT. Aparte de eso, no hay diferencia.
La mejor opción es usar un private final Object lock = new Object()
interno private final Object lock = new Object()
para evitar que alguien pueda bloquear potencialmente su monitor. Logra el mismo resultado sin la desventaja del bloqueo exterior maléfico. Tienes ese byte extra, pero rara vez hace una diferencia.
Entonces diría que no, no use la palabra clave synchronized
en la firma. En su lugar, usa 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
}
}
}
}
Editar 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 makeExactSize(5)
el tamaño de la matriz, alguien llama a makeExactSize(5)
en un subproceso diferente. Ahora, de repente, intentas acceder a los data[6]
y te bombardean. Se supone que la sincronización evita que eso suceda. En los programas 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;
}
}
Ir a través de los comentarios de código y descomentar y ejecutar los diferentes bloques para ver claramente la diferencia, la sincronización de notas tendrá una diferencia solo si se utiliza la misma instancia ejecutable, si cada subproceso iniciado obtiene un nuevo ejecutable no hará 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) {}
}
}
}
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 encontrado en más de 21 años de Java.
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 "sincronice", de lo contrario no será 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 referencia es atómica
Básicamente, eso es lo que necesitas pensar el 99% del tiempo cuando se trata de multihilo