Mi respuesta tiene cierta información específica de la implementación. Se basa en mi conocimiento práctico de Sun JVM y otros comportamientos de biblioteca de subprocesos.
Si dos subprocesos del productor invocan notificar, ¿se garantiza que se despierten dos subprocesos de consumidor distintos en espera?
No, no lo es. No hay garantía de que habrá consumidores despertados. Lo que está garantizado es que si hay 2 subprocesos en espera, entonces 2 diferentes subprocesos se colocarán en la cola de ejecución.
O puede ser que dos notify()
s despedido poco después de la otra causa el mismo hilo comsumer a poner en cola para despertar dos veces?
No. Two notify()
llamadas no hará que el mismo hilo de consumidor se ponga en cola dos veces. Sin embargo, puede provocar que se despierte un subproceso y que no haya otros subprocesos esperando, por lo que la segunda llamada notify()
puede no hacer nada. Por supuesto, el hilo podría haberse despertado y luego volverse a esperar otra vez, así que obtenga la segunda llamada notify()
de esa manera, pero no creo que eso sea lo que está preguntando.
¿Tiene java algo de funcionamiento interno atómico para despertar los hilos exactamente una vez?
Sí. El código Thread
tiene una cantidad de puntos de sincronización. Una vez que un hilo ha sido notificado, se retira de la cola wait
. Las futuras llamadas al notify()
buscarán en la cola wait
y no encontrarán el hilo.
Uno más importante punto. Con los modelos de productor/consumidor, siempre asegúrese de estar probando la condición en un bucle while
. La razón es que hay condiciones de carrera con los consumidores que están bloqueados en el candado pero que no esperan la condición.
synchronized (workQueue) {
// you must do a while here
while (workQueue.isEmpty()) {
workQueue.wait();
}
workQueue.remove();
}
Consumer1
podría estar esperando en workQueue
. Consumer2
podría bloquearse en synchronized
pero en la cola de ejecución. Si se pone algo en workQueue
y workQueue.notify()
se llama. Consumer2
se pone en cola de ejecución ahora pero es detrás deConsumer1
que está allí primero. Esta es una implementación común. Por lo tanto, Consumer1
va a eliminar el artículo del workQueue
sobre el que se notificó Consumer2
. Consumer2
tiene que volver a probar si el workQueue
está vacío; de lo contrario, remove()
arrojará porque la cola está vacía de nuevo. Consulte aquí para more details of the race.
También es importante tener en cuenta que los despertadores espurios se han documentado, por lo que el lazo while
protege contra un hilo que se despierta sin una llamada wait()
.
Dicho todo esto, si puede reducir su código de productor/consumidor utilizando un BlockingQueue
como se recomienda en otras respuestas, entonces debe hacerlo. El código BlockingQueue
ya ha resuelto todos estos problemas.
¿Por qué no solo utiliza una implementación de BlockingQueue? –
Estoy de acuerdo con Mauricio. Manténgase alejado de wait()/notify() si es posible - es un nido de abejas de condiciones de carrera, API mal documentadas y "wake-ups espurios". –