tl; dr :
Hay 3 problemas principales con multihilo:
1) Condiciones de carrera
2) Caché / memoria obsoleta
3) Cumplidor y optimizaciones de CPU
volatile
puede resolver 2 y 3, pero no puede resolver 1. synchronized
/ los bloqueos explícitos pueden resolver 1, 2 y 3.
Elaboración :
1) Considere este código inseguro de hilo:
x++;
Si bien puede parecer una operación, en realidad es 3: leer el valor actual de x de la memoria, agregarle 1 y guardarlo nuevamente en la memoria. Si pocos subprocesos intentan hacerlo al mismo tiempo, el resultado de la operación no está definido. Si x
originalmente era 1, después de que 2 hilos operen el código, puede ser 2 y puede ser 3, dependiendo de qué hilo completó qué parte de la operación antes de que el control se transfiriera al otro hilo. Esta es una forma de condición de carrera .
El uso synchronized
de un bloque de código lo hace atómico , lo que significa que las 3 operaciones suceden a la vez, y no hay forma de que otro hilo se interponga en el medio. Así que si x
era 1 y 2 hilos tratan de preformas x++
que sabemos que al final será igual a 3. Por lo que resuelve el problema de condición de carrera.
synchronized (this) {
x++; // no problem now
}
Calificación x
como volatile
no hace x++;
atómico, por lo que no resuelve este problema.
2) Además, los subprocesos tienen su propio contexto, es decir, pueden almacenar en caché los valores de la memoria principal. Eso significa que algunos subprocesos pueden tener copias de una variable, pero operan en su copia de trabajo sin compartir el nuevo estado de la variable entre otros subprocesos.
Tengamos en cuenta que en un hilo, x = 10;
. Y un poco más tarde, en otro hilo, x = 20;
. Es posible que el cambio en el valor de x
no aparezca en el primer subproceso, porque el otro subproceso ha guardado el nuevo valor en su memoria de trabajo, pero no lo ha copiado en la memoria principal. O que lo copió a la memoria principal, pero el primer hilo no ha actualizado su copia de trabajo. Entonces, si ahora el primer hilo verificaif (x == 20)
la respuesta será false
.
Marcar una variable como volatile
básicamente le dice a todos los hilos que hagan operaciones de lectura y escritura solo en la memoria principal.synchronized
le dice a cada subproceso que actualice su valor de la memoria principal cuando ingresan al bloque y que devuelva el resultado a la memoria principal cuando salga del bloque.
Tenga en cuenta que, a diferencia de las carreras de datos, la memoria obsoleta no es tan fácil de (re) producir, ya que de todos modos se producen descargas en la memoria principal.
3) El compilador y la CPU pueden (sin ninguna forma de sincronización entre subprocesos) tratar todo el código como subproceso único. Lo que significa que puede ver algún código, que es muy significativo en un aspecto de subprocesos múltiples, y tratarlo como si fuera un solo subproceso, donde no es tan significativo. Por lo tanto, puede mirar un código y decidir, en aras de la optimización, reordenarlo o incluso eliminar partes de él por completo, si no sabe que este código está diseñado para funcionar en múltiples hilos.
Considere el siguiente código:
boolean b = false;
int x = 10;
void threadA() {
x = 20;
b = true;
}
void threadB() {
if (b) {
System.out.println(x);
}
}
Pensaría que threadB solo podría imprimir 20 (o no imprimir nada en absoluto si se ejecuta threadB if-check antes de establecerlo b
en verdadero), ya que b
se establece en verdadero solo después de que x
se establece en 20, pero el compilador / CPU podría decidir reordenar threadA, en ese caso, threadB también podría imprimir 10. Marcado b
como volatile
asegura que no se reordenará (o descartará en ciertos casos). Lo que significa que threadB solo podría imprimir 20 (o nada en absoluto). Marcar los métodos como sincronizados logrará el mismo resultado. También marcando una variable comovolatile
solo asegura que no se reordenará, pero todo antes / después aún se puede reordenar, por lo que la sincronización puede ser más adecuada en algunos escenarios.
Tenga en cuenta que antes de Java 5 New Memory Model, volatile no resolvía este problema.