2012-07-21 5 views
6

Ho un ciclo while in esecuzione nel metodo principale del mio programma Java. Si suppone che il ciclo venga eseguito finché una variabile flag booleana non è impostata su true nel metodo keyPress del programma (ho aggiunto il programma come KeyListener a un JFrame).Mentre il ciclo non termina quando il flag è cambiato in thread diversi

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) {} 
} 

E 'la mia comprensione che keyPressed metodi vengono eseguiti nelle loro discussioni, così sembra come quando ho colpito un tasto, la variabile 'flag' deve essere impostata su true, e il ciclo durante l'esecuzione nel metodo main dovrebbe finire

TUTTAVIA, quando eseguo questo programma, il ciclo gira per sempre, anche se possiamo vedere che la variabile 'flag' è impostata su true! Stranamente, il programma si comporta correttamente se inserisco un rapido System.out.println echo della variabile 'flag' all'interno del ciclo while, ma ovviamente non voglio stampare nulla nel ciclo.

Sto indovinando che questo problema potrebbe essere il risultato del compilatore Java che tenta di ottimizzare il ciclo while vuoto fino al punto in cui si ferma effettivamente controllando la variabile 'flag'? Qualcuno ha suggerimenti per farlo funzionare correttamente, o forse alcuni approcci più concreti basati sulla concorrenza per fare in modo che il thread principale si interrompa fino a quando non viene eseguito il thread keyPressed?

Grazie!

+0

Utilizzare custodito blocchi invece (http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html). Andare in loop e aspettarsi che qualcosa diventi realtà rovinerà le prestazioni della tua applicazione e la renderà insensibile. –

risposta

9

Devi dichiarare la bandiera volatile, altrimenti il ​​compilatore può ottimizzare il tuo codice e saltare le letture della bandiera.

+0

Grande, questo lo ha risolto. Grazie! – user1543221

4

Mentre la soluzione volatile che altri hanno proposto dovrebbe funzionare, a meno che non è necessario il codice nel ciclo while per eseguire in modo continuo, probabilmente si dovrebbe invece usare wait() e notify() (o notifyAll()) all'interno di una sezione sincronizzato (da evitare "occupato in attesa"). Qualcosa di simile:

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(); 
     } 
    } 

    ... 

In caso contrario, si sta più volte sprecando cicli sul thread principale controllando il valore di flag più e più volte (che, in generale, se capita abbastanza, può rallentare la CPU per altri thread e può consumare la batteria sui dispositivi mobili). Questo è esattamente il tipo di situazione per cui è stato progettato wait().

0

1. è necessario utilizzare volatile parola chiave per la bandiera, Perché quando noi chiamiamo volatile una variabile di istanza, quindi la sua come dire alla JVM di fare in modo che il filo accedervi deve conciliare la propria copia di questa variabile di istanza con quella memorizzata nella memoria.

2. volatile sarà anche aiuto nel prevenire la memorizzazione nella cache del valore nel filo.

3. volatile farà quella variabile riflette il suo valore modificato in un filo verso un'altra, ma che non impedisce la condizione di competizione.

4. in modo da utilizzare parola chiave sincronizzato sul bilancio Atomic, che è l'impostazione il valore della bandiera.

Esempio:

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