java linux multithreading pthreads posix

Forzando un espurio-despertar en Java



linux multithreading (6)

Esta pregunta no se trata de si las activaciones falsas son realmente felices, ya que esto ya se discutió en detalle aquí: ¿Se producen realmente activaciones falsas? Por lo tanto, tampoco se trata de por qué tengo que poner un bucle alrededor de mi Declaración de wait . De qué se trata:

Me gustaría construir un caso , donde ocurre el despertar espurio. Lo que he aprendido hasta ahora en la pregunta vinculada anteriormente es esto:

Si se señala un proceso de Linux, cada uno de sus hilos en espera disfrutará de una buena activación espúrea.

Así que parece que esto solo funcionará en una máquina Linux, de hecho tengo Ubuntu 11.04 - 64-Bit. He escrito un programa Java con un subproceso esperando una condición, pero sin bucle y otra clase en la que un subproceso solo espera y recibe notificación de otro subproceso. Pensé que lanzar los tres subprocesos en una JVM forzaría el caso descrito anteriormente, pero parece que este no es el caso.

¿Alguien más tiene otra idea de cómo construir un caso así en Java?


"Spurious wakeup" es un hotchpotch y cubre cualquier detalle de implementación en ese ámbito. Por lo tanto, es bastante difícil entender qué es una activación falsa "real" y por qué otra es "irreal", y mucho menos en qué capa se origina este detalle de implementación. Elija cualquiera de "kernel", "system library (libc)", "JVM", "Java standart library (rt.jar)" o un marco personalizado construido sobre esta pila.

El siguiente programa muestra una activación falsa usando java.util.concurrent stuff:

import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SpuriousWakeupRWLock { static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); static int itemsReady; public static void main(String[] args) throws Exception { // let consumer 1 enter condition wait new ConsumerOne().start(); Thread.sleep(500); lock.lock(); try { // let consumer 2 hit the lock new ConsumerTwo().start(); Thread.sleep(500); // make condition true and signal one (!) consumer System.out.println("Producer: fill queue"); itemsReady = 1; condition.signal(); Thread.sleep(500); } finally { // release lock lock.unlock(); } System.out.println("Producer: released lock"); Thread.sleep(500); } abstract static class AbstractConsumer extends Thread { @Override public void run() { lock.lock(); try { consume(); } catch(Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } abstract void consume() throws Exception; } static class ConsumerOne extends AbstractConsumer { @Override public void consume() throws InterruptedException { if( itemsReady <= 0 ){ // usually this is "while" System.out.println("One: Waiting..."); condition.await(); if( itemsReady <= 0 ) System.out.println("One: Spurious Wakeup! Condition NOT true!"); else { System.out.println("One: Wakeup! Let''s work!"); --itemsReady; } } } } static class ConsumerTwo extends AbstractConsumer { @Override public void consume() { if( itemsReady <= 0 ) System.out.println("Two: Got lock, but no work!"); else { System.out.println("Two: Got lock and immediatly start working!"); --itemsReady; } } } }

Salida:

One: Waiting... Producer: fill queue Producer: released lock Two: Got lock and immediatly start working! One: Spurious Wakeup! Condition NOT true!

El JDK utilizado fue:

java version "1.6.0_20" OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2) OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)

Se basa en un detalle de implementación en java.util.concurrent : El Lock estándar tiene una cola de espera, la Condition tiene otra cola de espera. Si se señala la condición, el hilo señalado se mueve de la cola de la condición a la cola de la cerradura. El detalle de la implementación: se mueve al final de la cola . Si otro hilo ya está esperando en la cola de bloqueo y este segundo hilo no visitó la variable de condición, este hilo puede "robar" la señal. Si la implementación hubiera puesto el primer hilo antes del segundo hilo, esto no habría ocurrido. Este "bono" podría / estaría basado en el hecho de que el primer hilo ya tuvo el bloqueo una sola vez y que el tiempo de espera en la condición asociada con el mismo bloqueo se acredita a ese hilo.

Defino esto como "espurio" porque

  • la condición ha sido señalada una sola vez,
  • solo un hilo ha sido despertado por la condición
  • Pero el hilo despertado por la condición encontrada no era cierto.
  • el otro hilo nunca tocó la condición y por lo tanto es "afortunado pero inocente"
  • una implementación ligeramente diferente hubiera evitado esto.

El último punto se demuestra con este código usando Object.wait() :

public class SpuriousWakeupObject { static Object lock = new Object(); static int itemsReady; public static void main(String[] args) throws Exception { // let consumer 1 enter condition wait new ConsumerOne().start(); Thread.sleep(500); // let consumer 2 hit the lock synchronized (lock) { new ConsumerTwo().start(); Thread.sleep(500); // make condition true and signal one (!) consumer System.out.println("Producer: fill queue"); itemsReady = 1; lock.notify(); Thread.sleep(500); } // release lock System.out.println("Producer: released lock"); Thread.sleep(500); } abstract static class AbstractConsumer extends Thread { @Override public void run() { try { synchronized(lock){ consume(); } } catch(Exception e){ e.printStackTrace(); } } abstract void consume() throws Exception; } static class ConsumerOne extends AbstractConsumer { @Override public void consume() throws InterruptedException { if( itemsReady <= 0 ){ // usually this is "while" System.out.println("One: Waiting..."); lock.wait(); if( itemsReady <= 0 ) System.out.println("One: Spurious Wakeup! Condition NOT true!"); else { System.out.println("One: Wakeup! Let''s work!"); --itemsReady; } } } } static class ConsumerTwo extends AbstractConsumer { @Override public void consume() { if( itemsReady <= 0 ) System.out.println("Two: Got lock, but no work!"); else { System.out.println("Two: Got lock and immediatly start working!"); --itemsReady; } } } }

Salida:

One: Waiting... Producer: fill queue Producer: released lock One: Wakeup! Let''s work! Two: Got lock, but no work!

Aquí la implementación parece hacer lo que yo esperaría: el hilo que utiliza la condición se despierta primero.

Nota final: la idea del principio proviene de ¿Por qué java.util.concurrent.ArrayBlockingQueue usa ''while'' en lugar de ''if'' alrededor de las llamadas a la espera ()? , aunque mi interpretación es diferente y el código es de mí mismo.


AFAIK, la JVM de Sun utiliza "subprocesos verdes", también conocidos como subprocesos de nivel de usuario. Esto significa que los subprocesos de la JVM y los subprocesos del kernel no tienen que asignarse realmente de 1 a 1. Por lo tanto, a menos que la especificación lo indique, no veo por qué la JVM se conformaría con el comportamiento de POSIX.

Entonces, a pesar de que la especificación se refiere a la posibilidad de despertares espurios, debería ser difícil construir una prueba determinista que cause uno. Teniendo en cuenta que los subprocesos del núcleo que se ejecutan dentro de la activación de JVM a la señal, ¿cuántos subprocesos verdes se despertarán? ¿Uno? ¿Diez? ¿Ninguna? Quién sabe.


Encontré un reproductor que provoca activaciones espurias en el error 6454029 de Java. Comienza con 30, 60 y luego 100 pares de camareros / notificadores y hace que esperen y notifiquen un número específico de veces. Utiliza Object.wait () y Object.notify () estándar en lugar de los objetos de bloqueo y condición de nivel superior. Me las arreglé para usarlo para provocar activaciones falsas en mi máquina Linux de 64 bits con el valor de argumento de 1000000 con java 1.8.0-b132 y 1.6.0_45. Tenga en cuenta que el archivador original se quejaba de Windows XP, así que, presumiblemente, esto funciona también en al menos una versión de Windows.


La pregunta original a la que te has referido (en cuanto a artículo de wikipedia) dice que las activaciones espurias ocurren en la implementación de linux de pthread, como efecto secundario del proceso que se está señalando . Por su pregunta, me parece que perdió la "señal" (que es el método de comunicación entre procesos de Linux) con Object.notify () (que es el método de comunicación interno entre subprocesos de Java).

Si desea observar una activación falsa, debe ejecutar su programa java e intentar enviarle alguna señal.


No se puede forzar una activación falsa, pero para el hilo en ejecución, una activación falsa es indistinguible de una activación normal (la fuente del evento es diferente, pero el evento en sí es el mismo)

Para simular una activación falsa, simplemente llame a notify() ;

Llamar a interrupt() no es adecuado, porque al hacerlo establece el indicador de interrupción, y después de una activación falsa, el indicador de interrupción no se establece


Probé una prueba simple en Linux, enviando señales de proceso Java simples (como SALIR, PARAR, CONT, etc.). Estos no parecían causar un despertar espurio.

Entonces (al menos para mí) todavía no está claro en qué condiciones una señal de Linux causará una activación falsa en Java.