2015-11-30 11 views
6

In questo esempio ho un semplice JFrame contenente un JButton con un ActionListener ad esso associato. Questo AcitonListener modifica semplicemente un flag booleano che dovrebbe consentire il completamento del programma.I loop while in Java non verificano le loro condizioni se non c'è il corpo?

public class Test { 
    public static void main(String[] args){ 
     final boolean[] flag = new boolean[1]; 
     flag[0] = false; 
     JFrame myFrame = new JFrame("Test"); 
     JButton myButton = new JButton("Click Me!"); 
     myButton.addActionListener(new ActionListener(){ 
      @Override 
      public void actionPerformed(ActionEvent arg0) { 
       System.out.println("Button was clicked!"); 
       flag[0] = true; 
      } 
     }); 
     myFrame.add(myButton); 
     myFrame.setSize(128,128); 
     myFrame.setVisible(true); 
     System.out.println("Waiting"); 
     while(!flag[0]){} 
     System.out.println("Finished"); 
    } 
} 

questo non è mai stampe "finito", e dopo che il pulsante è stato fatto clic stamperà una volta

Waiting 
Button was clicked! 

Tuttavia, se modifico il ciclo while per leggere

while(!flag[0]){ 
    System.out.println("I should do nothing. I am just a print statement."); 
} 

Questo funziona! La stampa sembra

Waiting 
I should do nothing. I am just a print statement. 
I should do nothing. I am just a print statement. 
.... 
I should do nothing. I am just a print statement. 
Button was clicked! 
Finished 

Capisco questo probabilmente non è il modo corretto di aspettare su un'azione, ma comunque Sono interessato a sapere perché Java si comporta in questo modo.

+6

Date un'occhiata a questo http://docs.oracle.com/javase/tutorial/uiswing/concurrency/ – 3kings

risposta

7

Il motivo più probabile è che flag[0] = true; venga eseguito sul thread dell'interfaccia utente, mentre while(!flag[0]) viene eseguito sul thread principale.

Senza sincronizzazione, non è possibile garantire che la modifica apportata nel thread dell'interfaccia utente sia visibile dal thread principale.

Aggiungendo il System.out.println si introduce un punto di sincronizzazione (perché il metodo println è synchronized) e il problema viene risolto.

È possibile effettuare flag un'istanza volatile o una variabile booleana di classe (non un array) o, più semplicemente, inserire qualsiasi codice che si desidera venga eseguito sul pulsante premuto nel listener stesso.


Per riferimento, il codice con una variabile volatile, sarebbe simile a questa:

private static volatile boolean flag; 
public static void main(String[] args) { 
    JFrame myFrame = new JFrame("Test"); 
    JButton myButton = new JButton("Click Me!"); 
    myButton.addActionListener(new ActionListener() { 
    @Override public void actionPerformed(ActionEvent arg0) { 
     System.out.println("Button was clicked!"); 
     flag = true; 
    } 
    }); 
    myFrame.add(myButton); 
    myFrame.setSize(128, 128); 
    myFrame.setVisible(true); 
    System.out.println("Waiting"); 
    while (!flag) { } 
    System.out.println("Finished"); 
} 
+2

Anche 'while (! Flag [0]) {}' è estremamente aggressivo sulla CPU. Suppongo che OP stia usando Java 7 dal momento che deve dichiarare la finale booleana. Con Java 8, poteva usare direttamente un 'booleano volatile '. –

+1

@GuillaumeF. non puoi dichiarare una variabile volatile locale - dovrebbe essere un campo quindi non ha bisogno di essere definitivo in Java 7 o 8. – assylias

+0

Oh, hai ragione riguardo alla limitazione volatile locale. Grazie –