2012-07-21 13 views
6

Tengo un ciclo while ejecutándose en el método principal de mi programa Java. Se supone que el ciclo se ejecuta hasta que una variable de indicador booleano se establece en verdadero en el método keyPressed del programa (agregué el programa como KeyListener a JFrame).Mientras el ciclo no finaliza cuando la bandera cambia en un hilo diferente

import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 
import javax.swing.JFrame; 

public class ThreadWhile implements KeyListener { 
    private boolean flag = false; 

    public static void main(String[] args) { 
     //Set up key listening on a dummy JFrame 
     JFrame frame = new JFrame("Key Detector 9000"); 
     frame.setVisible(true); 
     ThreadWhile tw = new ThreadWhile(); 
     frame.addKeyListener(tw); 

     System.out.println("Looping until flag becomes true..."); 
     while(!tw.flag) { 
      //Commenting the println out makes the loop run forever! 
      System.out.println(tw.flag); 
     } 
     System.out.println("Flag is true, so loop terminated."); 
     System.exit(0); 
    } 

    public void keyPressed(KeyEvent e) { 
     flag = true; 
     System.out.println("flag: " + flag); 
    } 

    public void keyReleased(KeyEvent e) {} 
    public void keyTyped(KeyEvent e) {} 
} 

Es mi entendimiento de que keyPressed métodos se ejecutan en sus propios hilos, por lo que parece cuando pulso una tecla, la variable 'bandera' se debe establecer en true, y el bucle mientras se ejecuta en el método main debería terminar

SIN EMBARGO, cuando ejecuto este programa, el ciclo se ejecuta para siempre, ¡aunque podemos ver que la variable 'flag' se está configurando en verdadero! Curiosamente, el programa se comporta correctamente si inserto un eco de System.out.println rápido de la variable 'flag' dentro del ciclo while, pero obviamente no quiero imprimir nada en el ciclo.

Supongo que este problema podría ser el resultado de que el compilador de Java intente optimizar el ciclo while vacío hasta el punto en que deja de verificar realmente la variable 'flag'? ¿Alguien tiene sugerencias para hacer que esto funcione correctamente, o tal vez algunos enfoques más ingeniosos basados ​​en simultaneidad para hacer que el hilo principal se detenga hasta que se ejecute el subproceso keyPressed?

Gracias!

+0

Use bloques en lugar vigilado (http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html). Looping y esperar que algo se vuelva cierto arruinará el rendimiento de su aplicación y la dejará sin respuesta. –

Respuesta

9

Debe declarar la bandera volatile, de lo contrario, el compilador puede optimizar su código y omitir las lecturas de la bandera.

+0

Genial, eso lo resolvió. ¡Gracias! – user1543221

4

Mientras que la solución volatile que otros propusieron debería funcionar, a menos que necesite el código en el bucle while para ejecutar de forma continua, probablemente debería utilizar en su lugar wait() y notify() (o notifyAll()) en el interior de una sección sincronizada (para evitar "ocupado esperando"). Algo así como:

public class ThreadWhile implements KeyListener { 
    private boolean flag = false; 
    private Object flagLock = new Object(); 

    public static void main(String[] args) { 
     //Set up key listening on a dummy JFrame 
     JFrame frame = new JFrame("Key Detector 9000"); 
     frame.setVisible(true); 
     ThreadWhile tw = new ThreadWhile(); 
     frame.addKeyListener(tw); 

     System.out.println("Waiting until flag becomes true..."); 
     synchronized (tw.flagLock) { 
      while(!tw.flag) 
       tw.flagLock.wait(); // Note: this suspends the thread until notification, so no "busy waiting" 
     } 
     System.out.println("Flag is true, so loop terminated."); 
     System.exit(0); 
    } 

    public void keyPressed(KeyEvent e) { 
     synchronized (flagLock) { 
      flag = true; 
      System.out.println("flag: " + flag); 
      flagLock.notifyAll(); 
     } 
    } 

    ... 

De lo contrario, usted está perdiendo repetidamente ciclos en el hilo principal comprobando el valor de flag una y otra (que, en general, si sucede lo suficiente, puede ralentizar la CPU para otros hilos y puede desgastar la batería en dispositivos móviles). Este es exactamente el tipo de situación para la que se diseñó wait().

0

1. Es necesario utilizar volatile palabra clave para el indicador, ya que cuando llamamos volátil en una variable de instancia, a continuación, es como decir la JVM para asegurarse de que el hilo acceder a él debe conciliar su propia copia de este variable de instancia con la almacenada en la memoria.

2. volátil también ayuda en la prevención de la caché del valor en el hilo.

3. volátil hará que esa variable refleja su valor cambiado en un hilo a la otra, pero eso no impide la condición de carrera.

4. Así que use palabra clave sincronizada en las declaraciones Atomic que establece el valor de la bandera.

Ej:

public void keyPressed(KeyEvent e) { 
    synchronized(this){ 
     flag = true; 
     System.out.println("flag: " + flag); 
     } 
    } 
Cuestiones relacionadas