Il ciclo occupato in altri thread ritarda l’elaborazione EDT

Ho un programma Java che esegue un ciclo stretto su un thread separato (non-EDT). Anche se penserei che l’interfaccia utente di Swing dovrebbe essere ancora retriggers, non lo è. Il seguente esempio mostra il problema: facendo clic sul pulsante “Prova” dovrebbe apparire una finestra più o meno un secondo dopo, e dovrebbe essere ansible chiudere immediatamente quella finestra facendo clic su una delle sue risposte. Invece, la finestra di dialogo impiega molto più tempo per apparire e / o richiede molto tempo per chiudersi dopo aver cliccato su uno dei pulsanti.

  • Il problema si verifica su Linux (due macchine diverse con distribuzioni diverse), su Windows, sul Raspberry Pi (solo server VM) e su Mac OS X (segnalato da un altro utente SO).
  • Java versione 1.8.0_65 e 1.8.0_72 (provato entrambi)
  • processore i7 con molti core. L’EDT dovrebbe disporre di molta potenza di elaborazione disponibile.

Qualcuno ha idea del motivo per cui l’elaborazione EDT viene ritardata, anche se c’è un solo thread occupato?

(Si noti che nonostante vari suggerimenti sulla chiamata Thread.sleep siano la causa del problema, non lo è. Può essere rimosso e il problema può ancora essere riprodotto, anche se si manifesta leggermente meno frequentemente e di solito mostra il secondo comportamento descritto sopra – cioè, la finestra di dialogo JOptionPane non retriggers piuttosto che l’aspetto della finestra di dialogo ritardata.Inoltre, non vi è alcun motivo per cui la chiamata di JOptionPane debba essere restituita all’altro thread perché ci sono core di processore di riserva come menzionato sopra; l’EDT potrebbe continuare a funzionare su un altro core dopo il chiamare per sleep ).

 import java.awt.EventQueue; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; public class MFrame extends JFrame { public static void main(String[] args) { EventQueue.invokeLater(() -> { new MFrame(); }); } public MFrame() { JButton tryme = new JButton("Try me!"); tryme.addActionListener((e) -> { Thread t = new Thread(() -> { int a = 4; for (int i = 0; i < 100000; i++) { for (int j = 0; j < 100000; j++) { a *= (i + j); a += 7; } } System.out.println("a = " + a); }); t.start(); // Sleep to give the other thread a chance to get going. // (Included because it provokes the problem more reliably, // but not necessary; issue still occurs without sleep call). try { Thread.sleep(500); } catch (InterruptedException ie) { ie.printStackTrace(); } // Now display a dialog JOptionPane.showConfirmDialog(null, "You should see this immediately"); }); getContentPane().add(tryme); pack(); setVisible(true); } } 

Aggiornamento: il problema si verifica solo con il server VM ( ma vedi ulteriori aggiornamenti ). La specifica della VM del client (l’argomento della riga di comando -client su eseguibile java) sembra eliminare il problema ( aggiornamento 2 ) su una macchina ma non un’altra .

Aggiornamento 3: vedo l’utilizzo del processore del 200% dal processo Java dopo aver fatto clic sul pulsante, il che implica che ci sono 2 core del processore completamente caricati. Questo non ha senso per me.

Aggiornamento 4: si verifica anche su Windows.

Aggiornamento 5: l’ uso di un debugger (Eclipse) si rivela problematico; il debugger sembra incapace di fermare i thread. Questo è molto insolito, e sospetto che ci sia una sorta di livelock o race condition nella VM, quindi ho registrato un bug con Oracle (ID recensione JI-9029194).

Aggiornamento 6: Ho trovato la mia segnalazione di bug nel database dei bug di OpenJDK . (Non sono stato informato che era stato accettato, ho dovuto cercarlo). La discussione qui è più interessante e getta già un po ‘di luce su quale possa essere la causa di questo problema.

Vedo lo stesso effetto su Mac OS X. Sebbene il tuo esempio sia correttamente sincronizzato, la variabilità della piattaforma / JVM che vedi è probabilmente dovuta a capricci nel modo in cui i thread sono pianificati, risultando in carenza. L’aggiunta di Thread.yield() al ciclo esterno in mitiga il problema, come mostrato di seguito. Ad eccezione della natura artificiale dell’esempio, un suggerimento come Thread.yield() normalmente non è richiesto. In ogni caso, considera SwingWorker , qui mostrato, eseguendo un ciclo simile per scopi dimostrativi.

Non credo che Thread.yield() debba essere chiamato in questo caso, nonostante la natura artificiale del test case, comunque.

Corretta; la resa semplicemente espone il problema sottostante: t affama il thread di invio dell’evento. Si noti che la GUI si aggiorna prontamente nell’esempio seguente, anche senza Thread.yield() . Come discusso in questa correlata Q & A , puoi provare a ridurre la priorità del thread. In alternativa, esegui t in una JVM separata, come suggerito qui utilizzando ProcessBuilder , che può anche essere eseguito sullo sfondo di uno SwingWorker , come mostrato qui .

ma perché ?

Tutte le piattaforms supportate sono costruite su librerie grafiche a thread singolo. È abbastanza facile bloccare, affamare o saturare il thread di invio dell’evento governante. Le attività in background non banali generalmente producono implicitamente, come quando si pubblicano risultati intermedi, si bloccano per I / O o si attende una coda di lavoro. Un’attività che non funziona potrebbe dover fornire esplicitamente.

Immagine

 import java.awt.EventQueue; import javax.swing.JButton; import javax.swing.JFrame; public class MFrame extends JFrame { private static final int N = 100_000; private static final String TRY_ME = "Try me!"; private static final String WORKING = "Working…"; public static void main(String[] args) { EventQueue.invokeLater(new MFrame()::display); } private void display() { JButton tryme = new JButton(TRY_ME); tryme.addActionListener((e) -> { Thread t = new Thread(() -> { int a = 4; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { a *= (i + j); a += 7; } Thread.yield(); } EventQueue.invokeLater(() -> { tryme.setText(TRY_ME); tryme.setEnabled(true); }); System.out.println("a = " + a); }); t.start(); tryme.setEnabled(false); tryme.setText(WORKING); }); add(tryme); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } } 

Le mie osservazioni:

  • Sostituisci la filettatura con Swingworker:

    • Nessuna differenza
  • Sostituisci il filo con swingworker e fai un po ‘di lavoro all’interno del primo ciclo for:

    • Ho ottenuto i risultati attesi dall’interfaccia utente e non mi sono congelato e da qui in poi è stata una navigazione scorrevole

Con questo codice, si osserva il comportamento previsto:

 public class MFrame extends JFrame { public static void main(String[] args) { new MFrame(); } public MFrame() { JButton tryme = new JButton("Try me!"); tryme.addActionListener((e) -> { SwingWorker longProcess = new SwingWorker() { private StringBuilder sb = new StringBuilder(); @Override protected Void doInBackground() throws Exception { int a = 4; for (int i = 0; i < 100000; i++) { for (int j = 0; j < 100000; j++) { a *= (i + j); a += 7; } sb.append(a); // <-- this seems to be the key } System.out.println("a = " + a); return null; } @Override protected void done() { try { get(); System.out.println(sb.toString()); } catch (InterruptedException | ExecutionException e1) { e1.printStackTrace(); } } }; longProcess.execute(); // Sleep to give the other thread a chance to get going. // (Included because it provokes the problem more reliably, // but not necessary; issue still occurs without sleep call). try { Thread.sleep(500); } catch (InterruptedException ie) { ie.printStackTrace(); } // Now display a dialog SwingUtilities.invokeLater(() -> JOptionPane.showConfirmDialog(this, "You should see this immediately")); }); getContentPane().add(tryme); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setVisible(true); } } 

La stessa osservazione viene fatta usando il codice originale di OP:

  • Fai qualche altro lavoro all’interno del primo ciclo for
    • Ha ottenuto risultati attesi

Esempio

 tryme.addActionListener((e) -> { Thread t = new Thread(() -> { StringBuilder sb = new StringBuilder(); int a = 4; for (int i = 0; i < 100000; i++) { for (int j = 0; j < 100000; j++) { a *= (i + j); a += 7; } sb.append(a); // <-- again the magic seems to be on this line } System.out.println(sb.toString()); }); ... }); 

Sto usando Ubuntu 14.04 su una macchina semi potente.

 java version "1.8.0_72" Java(TM) SE Runtime Environment (build 1.8.0_72-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode) 

Cosa significano le mie osservazioni?

Non molto altro non tutto è perduto e qualcuno potrebbe aver ottimizzato un po 'troppo il compilatore, cosa che blocca in qualche modo il thread dell'interfaccia utente. Onestamente non sono sicuro di cosa significhi tutto ciò, ma sono sicuro che qualcuno lo capirà

  • per impostazione predefinita tutto è partito da EDT (in questo caso all’interno di ActionListener ) bloccato da Thread.sleep terminato quando tutto il codice viene eseguito incluso Thread.sleep , con l’ipotesi che tu abbia perso tutti gli eventi incl. pittura, per tutto il tempo cunsumed questo codice, tutti gli eventi sono dipinte alla fine e in una volta

  • questo codice ha perso autoclose JOptionPane , mai dipinto sullo schermo (simulazione di come comodamente Thread.sleep uccide la pittura in Swing)

  • La GUI Swing non è responsabile per il mouse o l’evento chiave, non è ansible terminare questa applicazione, questo è ansible solo da Runnable#Thread SwingWorker e SwingWorker , che sono designati per iniziare una nuova discussione, Thread ( Workers Thread ), durante qualsiasi cosa all’interno di Runnable#Thread e SwingWorker è task SwingWorker (o usando Runnable#Thread è ansible mettere in pausa, modificare …)

  • non si tratta di multithreading, né di condividere resourcer in un altro core (s), in Win10 tutti i core che mi mostrano, condividendo l’incremento in proporzione

uscita dal codice (leggermente modificato) (basato sul tuo SSCCE / MCVE)

 run: test started at - 16:41:13 Thread started at - 16:41:15 to test EDT before JOptionPane - true at 16:41:16 before JOptionPane at - 16:41:16 Thread ended at - 16:41:29 a = 1838603747 isEventDispatchThread()false after JOptionPane at - 16:41:29 Thread started at - 16:41:34 to test EDT before JOptionPane - true at 16:41:34 before JOptionPane at - 16:41:34 Thread ended at - 16:41:47 a = 1838603747 isEventDispatchThread()false after JOptionPane at - 16:41:47 BUILD SUCCESSFUL (total time: 38 seconds) 

ancora una volta autoclose JOptionPane non sarà mai dipinto sullo schermo (testato win10-64b, i7, Java8), probabilmente fino a Java 1.6.022 tutto verrà dipinto e correttamente (AFAIK l’ultima correzione per edt e da questo momento SwingWorker funziona senza bug)

 import java.awt.Component; import java.awt.EventQueue; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.SimpleDateFormat; import java.util.Date; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.Timer; public class MFrame extends JFrame { public static void main(String[] args) { EventQueue.invokeLater(() -> { new MFrame(); }); } public MFrame() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); System.out.println("test started at - " + sdf.format(getCurrDate().getTime())); //http://stackoverflow.com/a/18107432/714968 Action showOptionPane = new AbstractAction("show me pane!") { private static final long serialVersionUID = 1L; @Override public void actionPerformsd(ActionEvent e) { createCloseTimer(3).start(); System.out.println("before JOptionPane at - " + sdf.format(getCurrDate().getTime())); JOptionPane.showMessageDialog((Component) e.getSource(), "nothing to do!"); } private Timer createCloseTimer(int seconds) { ActionListener close = new ActionListener() { @Override public void actionPerformsd(ActionEvent e) { Window[] windows = Window.getWindows(); for (Window window : windows) { if (window instanceof JDialog) { JDialog dialog = (JDialog) window; if (dialog.getContentPane().getComponentCount() == 1 && dialog.getContentPane().getComponent(0) instanceof JOptionPane) { dialog.dispose(); System.out.println("after JOptionPane at - " + sdf.format(getCurrDate().getTime())); } } } } }; Timer t = new Timer(seconds * 1000, close); t.setRepeats(false); return t; } }; JButton tryme = new JButton("Try me!"); tryme.addActionListener((e) -> { System.out.println("Thread started at - " + sdf.format(getCurrDate().getTime())); Thread t = new Thread(() -> { int a = 4; for (int i = 0; i < 100000; i++) { for (int j = 0; j < 100000; j++) { a *= (i + j); a += 7; } } System.out.println("Thread ended at - " + sdf.format(getCurrDate().getTime())); System.out.println("a = " + a); System.out.println("isEventDispatchThread()" + SwingUtilities.isEventDispatchThread()); }); t.start(); // Sleep to give the other thread a chance to get going: try { Thread.sleep(500); } catch (InterruptedException ie) { ie.printStackTrace(); } // Now display a dialog System.out.println("to test EDT before JOptionPane - " + SwingUtilities.isEventDispatchThread() + " at " + sdf.format(getCurrDate().getTime())); showOptionPane.actionPerformed(e); }); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(tryme); pack(); setLocation(150, 150); setVisible(true); } private Date getCurrDate() { java.util.Date date = new java.util.Date(); return date; } } 

nota da testare con Runnable#Thread e SwingWorker

Questa non è una risposta definitiva, ma si avvicina alla comprensione del problema.

Ho provato a minimizzare il codice per rimuovere potenziali insidie ​​con sleep e actionPerformsd e credo di averlo fatto mantenendo intatto il problema:

 public class MFrame extends JFrame { public static void main(String[] args) { EventQueue.invokeLater(() -> new MFrame()); } public MFrame() { Thread t = new Thread(() -> { int a = 4; for (int i = 0; i < 50000; i++) { for (int j = 0; j < 50000; j++) { a *= (i + j); a += 7; } } // System.out.println("c" + a); }); System.out.println("a"); // pack(); t.start(); // pack(); System.out.println("b"); } } 

Su Win7, i7 2670QM, JDK 1.8.0_25 ottengo i seguenti risultati:

Solo il 2 ° pack commentato:

 a b [pause] c-1863004573 

(previsto poiché anche senza sincronizzazione, la stampa di b verrebbe raggiunta prima di stampare c , a meno che forse non si sia su un processore superduper che potrebbe eseguire il calcolo più velocemente?)

Solo il primo pack commentato:

 a [pause] c-1863004573 b 

(inatteso)

Puoi confermare i miei risultati con / out il flag -client ?

Questo sembra essere un problema. Di seguito l’osservazione Event Thread del dispatcher ritarda l’elaborazione, avrebbe dovuto rispondere immediatamente:

  1. Esegui programma di esempio
  2. Fai clic sul pulsante “Prova”
  3. Fare clic su qualsiasi pulsante (sì / no / cancella) per chiudere la finestra di dialogo risultante

Su Windows

Lungo ritardo osservare tra il passaggio 2 e il passaggio 3.

Passaggio 3 -> chiude immediatamente la finestra di dialogo.

Su Linux

Passaggio 2 al passaggio 3: nessun ritardo.

Passaggio 3 -> ritardo lungo per chiudere la finestra di dialogo.