GNU/Linux >> Tutoriales Linux >  >> Linux

Forzar una activación espuria en Java

La pregunta original a la que se ha referido (hasta ahora, como artículo de wikipedia) dice que se producen activaciones falsas en la implementación de linux de pthread, como efecto secundario del proceso señalado . A partir de su pregunta, me parece que se 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 entre subprocesos interno de Java).

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


Intenté una prueba simple en Linux, enviando señales de proceso Java simples (como SALIR, DETENER, CONT, etc.). Estos no parecían causar una activación falsa.

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


"Spurious wakeup" es una mezcolanza y cubre cualquier detalles de implementación en ese ámbito. Por lo tanto, es bastante difícil distinguir qué es una activación espuria "real" y por qué otra es "irreal", y mucho menos en qué capa se origina este detalle de implementación. Elija cualquiera de "kernel", "biblioteca del sistema (libc)", "JVM", "biblioteca estándar de Java (rt.jar)" o un marco personalizado construido sobre esta pila.

El siguiente programa muestra una activación falsa 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 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 estándar Lock tiene una cola de espera, la Condition tiene otra cola de espera. Si se señala la condición, el subproceso señalado se mueve de la cola de la condición a la cola del bloqueo. El detalle de la implementación:se mueve al final de la cola . Si otro subproceso ya está esperando en la cola de bloqueo y este segundo subproceso no visitó la variable de condición, este subproceso puede "robar" la señal. Si la implementación hubiera puesto el primer hilo antes el segundo hilo, esto no hubiera pasado. Esta "bonificación" podría basarse en el hecho de que el primer subproceso ya obtuvo 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 "falso" porque

  • la condición se ha señalado una sola vez,
  • solo un subproceso ha sido activado por la condición
  • pero el hilo despertado por la condición descubrió que no era cierto
  • el otro hilo nunca tocó la condición y por lo tanto es "afortunado pero inocente"
  • una implementación ligeramente diferente 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 esperaría:el hilo que usa la condición se despierta primero.

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


No puede forzar una activación falsa, pero para el subproceso 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 notificar();

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


Linux
  1. Corrección E:No se pudo obtener el bloqueo /var/cache/apt/archives/lock [Consejo rápido]

  2. ¿Cómo matar el proceso que mantiene el bloqueo Apt?

  3. ¿Retraso de bloqueo de mayúsculas?

  4. Java 10 Webupd8?

  5. Cómo instalar Java en Ubuntu 22.04

Instalar Java manualmente en Linux

Cómo comprobar la versión de Java

Cómo instalar Java en Ubuntu 18.04

Instale la última versión de Java en Ubuntu 18.04

Cómo instalar Java en CentOS 8

¿Cómo instalar Java en Ubuntu 18.04?