A wait()
solo tiene sentido cuando también hay un notify()
, por lo que siempre se trata de la comunicación entre subprocesos, y eso necesita sincronización para funcionar correctamente. Se podría argumentar que esto debería ser implícito, pero eso realmente no ayudaría, por la siguiente razón:
Semánticamente, nunca solo wait()
. Necesitas alguna condición para ser satsificado, y si no es así, espera hasta que lo sea. Entonces, lo que realmente haces es
if(!condition){
wait();
}
Pero la condición está siendo establecida por un hilo separado, por lo que para que esto funcione correctamente, necesita sincronización.
Un par de cosas más mal con él, donde solo porque su hilo dejó de esperar no significa que la condición que está buscando sea verdadera:
Puede obtener despertadores espurios (lo que significa que un hilo puede despertarse de la espera sin haber recibido una notificación), o
La condición puede establecerse, pero un tercer subproceso vuelve a hacer que la condición sea falsa cuando el subproceso en espera se activa (y vuelve a adquirir el monitor).
Para tratar estos casos, lo que realmente necesita es siempre alguna variación de esto:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Mejor aún, no te metas con las primitivas de sincronización y trabaja con las abstracciones ofrecidas en los java.util.concurrent
paquetes.
Thread.interrupted()
.Vamos a ilustrar qué problemas tendríamos si
wait()
pudiéramos llamarnos fuera de un bloque sincronizado con un ejemplo concreto .Supongamos que implementamos una cola de bloqueo (lo sé, ya hay una en la API :)
Un primer intento (sin sincronización) podría verse algo en la línea de abajo
Esto es lo que podría suceder potencialmente:
Un hilo de consumidor llama
take()
y ve que elbuffer.isEmpty()
.Antes de que el hilo del consumidor continúe llamando
wait()
, aparece un hilo del productor e invoca un mensaje completogive()
, es decir,buffer.add(data); notify();
El hilo del consumidor ahora llamará
wait()
(y perderá elnotify()
que se acaba de llamar).Si no tiene suerte, el hilo productor no producirá más
give()
como resultado del hecho de que el hilo consumidor nunca se despierta, y tenemos un punto muerto.Una vez que comprenda el problema, la solución es obvia: use
synchronized
para asegurarse de quenotify
nunca se llame entreisEmpty
ywait
.Sin entrar en detalles: este problema de sincronización es universal. Como señala Michael Borgwardt, esperar / notificar se trata de comunicación entre subprocesos, por lo que siempre terminará con una condición de carrera similar a la descrita anteriormente. Esta es la razón por la cual se aplica la regla "solo esperar dentro de sincronizado".
Un párrafo del enlace publicado por @Willie lo resume bastante bien:
El predicado que el productor y el consumidor deben acordar está en el ejemplo anterior
buffer.isEmpty()
. Y el acuerdo se resuelve asegurando que la espera y la notificación se realicen ensynchronized
bloques.Esta publicación ha sido reescrita como un artículo aquí: Java: ¿Por qué esperar debe ser llamado en un bloque sincronizado?
fuente
return buffer.remove();
mientras bloquea pero despuéswait();
, ¿funciona?wait
retornos.Thread.currentThread().wait();
en lamain
función rodeado de try-catch paraInterruptedException
. Sinsynchronized
bloqueo, me da la misma excepciónIllegalMonitorStateException
. ¿Qué lo hace llegar al estado ilegal ahora? Sinsynchronized
embargo, funciona dentro del bloque.@Rollerball tiene razón. Se
wait()
llama al, para que el hilo pueda esperar a que ocurra alguna condición cuando se produce estawait()
llamada, el hilo se ve obligado a abandonar su bloqueo.Para renunciar a algo, primero debes tenerlo. El subproceso debe ser el propietario de la cerradura primero. De ahí la necesidad de llamarlo dentro de un
synchronized
método / bloque.Sí, estoy de acuerdo con todas las respuestas anteriores con respecto a los posibles daños / inconsistencias si no verificó la condición dentro del
synchronized
método / bloque. Sin embargo, como señaló @ shrini1000, solo llamarwait()
dentro del bloque sincronizado no evitará que ocurra esta inconsistencia.Aquí hay una buena lectura ...
fuente
El problema que puede causar si no sincroniza antes
wait()
es el siguiente:makeChangeOnX()
y comprueba la condición while, y estátrue
(x.metCondition()
devuelvefalse
, significa quex.condition
estáfalse
) para que pueda entrar. Luego, justo antes delwait()
método, otro subproceso va asetConditionToTrue()
y establece elx.condition
atrue
ynotifyAll()
.wait()
método (no afectado por lonotifyAll()
que sucedió unos momentos antes). En este caso, el primer subproceso permanecerá esperando que se ejecute otro subprocesosetConditionToTrue()
, pero es posible que eso no vuelva a suceder.fuente
Todos sabemos que los métodos wait (), notify () y notifyAll () se utilizan para las comunicaciones entre subprocesos. Para deshacerse de la señal perdida y los problemas espurios de despertar, el hilo de espera siempre espera en algunas condiciones. p.ej-
Luego, notificar los conjuntos de hilos wasNotified variable a true y notificar.
Cada subproceso tiene su caché local, por lo que todos los cambios primero se escriben allí y luego se promueven gradualmente a la memoria principal.
Si estos métodos no se invocan dentro del bloque sincronizado, la variable wasNotified no se enjuagaría en la memoria principal y estaría allí en la caché local del subproceso, por lo que el subproceso en espera seguirá esperando la señal aunque se restableció notificando el subproceso.
Para solucionar este tipo de problemas, estos métodos siempre se invocan dentro del bloque sincronizado, lo que garantiza que cuando se inicia el bloque sincronizado, todo se leerá desde la memoria principal y se enjuagará en la memoria principal antes de salir del bloque sincronizado.
Gracias, espero que te aclare.
fuente
Esto básicamente tiene que ver con la arquitectura de hardware (es decir, RAM y cachés ).
Si no lo usa
synchronized
junto conwait()
onotify()
, otro hilo podría ingresar al mismo bloque en lugar de esperar a que el monitor lo ingrese. Además, cuando, por ejemplo, acceda a una matriz sin un bloque sincronizado, es posible que otro subproceso no vea el cambio en él ... en realidad, otro subproceso no verá ningún cambio cuando ya tenga una copia de la matriz en el caché de nivel x ( también conocido como cachés de 1er / 2do / 3er nivel) del núcleo de la CPU que maneja el hilo.Pero los bloques sincronizados son solo un lado de la medalla: si realmente accede a un objeto dentro de un contexto sincronizado desde un contexto no sincronizado, el objeto aún no se sincronizará incluso dentro de un bloque sincronizado, porque contiene una copia propia del objeto en su caché. Escribí sobre estos problemas aquí: https://stackoverflow.com/a/21462631 y cuando un candado contiene un objeto no final, ¿puede la referencia del objeto ser cambiada por otro hilo?
Además, estoy convencido de que los cachés de nivel x son responsables de la mayoría de los errores de tiempo de ejecución no reproducibles. Esto se debe a que los desarrolladores generalmente no aprenden cosas de bajo nivel, como el funcionamiento de la CPU o cómo la jerarquía de memoria afecta el funcionamiento de las aplicaciones: http://en.wikipedia.org/wiki/Memory_hierarchy
Sigue siendo un enigma por qué las clases de programación no comienzan primero con la jerarquía de memoria y la arquitectura de la CPU. "Hola mundo" no ayudará aquí. ;)
fuente
directamente de este tutorial de oráculo de Java:
fuente
Cuando llama a notify () desde un objeto t, java notifica un método particular t.wait (). Pero, ¿cómo Java busca y notifica un método de espera en particular?
Java solo mira el bloque de código sincronizado que fue bloqueado por el objeto t. Java no puede buscar en todo el código para notificar a un t.wait () en particular.
fuente
según los documentos:
wait()
El método simplemente significa que libera el bloqueo del objeto. Por lo tanto, el objeto se bloqueará solo dentro del bloque / método sincronizado. Si el hilo está fuera del bloque de sincronización significa que no está bloqueado, si no está bloqueado, ¿qué liberarías en el objeto?fuente
Subproceso de espera en el objeto de supervisión (objeto utilizado por el bloque de sincronización), puede haber un número n de objeto de supervisión en todo el viaje de un único subproceso. Si el subproceso espera fuera del bloque de sincronización, entonces no hay ningún objeto de supervisión y tampoco otro subproceso notifica para acceder al objeto de supervisión, entonces, ¿cómo sabría el subproceso fuera del bloque de sincronización que ha sido notificado? Esta es también una de las razones por las que wait (), notify () y notifyAll () están en la clase de objeto en lugar de la clase de subproceso.
Básicamente, el objeto de supervisión es un recurso común aquí para todos los subprocesos, y los objetos de supervisión solo pueden estar disponibles en el bloque de sincronización.
fuente