Forzar un espurio-wake up en Java


Esta pregunta no se trata de, si espurias wakeups realmente feliz, porque esto ya se discutió en toda la longitud aquí: ¿Espurias wakeups realmente suceden? Por lo tanto esto tampoco es acerca de, por qué tengo que poner un bucle alrededor de mi wait Declaración. De qué se trata esto:

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

Si se indica un proceso Linux, hilos de espera cada uno disfrutar de un bonito, caliente espurio despertar.

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

Tiene a alguien más otra idea de cómo construir un caso de este tipo en Java?

Author: Community, 2011-07-15

6 answers

No se puede forzar un wakeup espurio, pero para el hilo en ejecución, un wakeup espurio es indistinguible de un wakeup regular (la fuente del evento es diferente, pero el evento en sí es el mismo)

Para simular un despertar espurio, simplemente llame a notify();

Llamar a interrupt() no es adecuado, porque al hacerlo se establece el indicador de interrupción, y después de un despertar espurio, el indicador de interrupción es no establecido

 17
Author: Bohemian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-09 06:14:30

"Spurious wakeup" es un hotchpotch y cubre cualquier detalle de implementación en ese reino. Por lo tanto, es bastante difícil entender qué es un despertar "real" espurio y por qué otro 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 un despertar espurio usando java.util.concurrent cosas:

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 usado 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 estándar Lock tiene una cola de espera, el Condition tiene otra cola de espera. Si se indica la condición, el hilo señalado se mueve de la cola de la condición a la cola del bloqueo. El detalle de 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 visita la variable condición, este hilo puede "robar" la señal. Si la implementación hubiera puesto el primer hilo antes de el segundo hilo, esto no habría sucedido. Este "bono" podría/estaría basado en el hecho de que el primer hilo ya tiene el bloqueo una 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{[13]]}

  • la condición ha sido señalizado una sola vez,
  • solo un hilo ha sido despertado por la condición
  • pero el hilo despertado por la condición encontró que no era cierto
  • el otro hilo nunca tocó la condición y por lo tanto es "afortunado pero inocente"
  • una aplicación ligeramente distinta habría 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 haría espéralo: El hilo que usa la condición se despierta primero.

Nota final: La idea para el principio viene de Por qué java.útil.concurrente.ArrayBlockingQueue usa bucles ' while 'en lugar de' if ' alrededor de llamadas a await ()?, aunque mi interpretación es diferente y el código es de mí mismo.

 9
Author: A.H.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:46:11

La pregunta original a la que se ha referido (en lo que respecta al artículo de wikipedia) dice que los wakeups espurios ocurren en la implementación de linux de pthread, como efecto secundario del proceso que se señala. A partir de su pregunta me parece que se perdió "signal" (que es el método de comunicación entre procesos de linux) con Object.notify () (que es el método de comunicación interna entre hilos de Java).

Si desea observar wakeup espurio must debe ejecutar su programa java y tratar de enviarlo algunos señal.

 2
Author: BegemoT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-19 08:21:15

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

Así que (al menos para mí) todavía no está claro bajo qué condiciones una señal de Linux causará un despertar espurio en Java.

 1
Author: Archie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-20 02:04:45

Encontré un reproductor que fuerza wakeups espurias en Java bug 6454029. Inicia 30, 60 y luego 100 pares de camareros / notificadores y hace que esperen y notifiquen un número específico de veces. Utiliza Objeto estándar.wait () y Object.notify () en lugar de los objetos de Bloqueo y Condición de nivel superior. He logrado usarlo para causar wakeups espurias a ocurrir 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 la el archivador original se quejaba de Windows XP, por lo que presumiblemente esto funciona en al menos un sabor de Windows también.

 1
Author: Mark Winterrowd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-27 18:11:17

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

Por lo tanto, aunque la especificación se refiere a la posibilidad de espurias despertares, debería ser difícil construir una prueba determinista que causa uno. Teniendo en cuenta que los hilos del kernel que se ejecutan dentro de la señal, ¿cuántos hilos verdes vas a despertar? Uno? Diez? ¿Ninguna? Quién sabe.

 0
Author: João Fernandes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-24 15:55:28