El bucle ocupado en otro hilo retrasa el procesamiento de EDT

Tengo un progtwig Java que ejecuta un ciclo cerrado en un hilo separado (no EDT). Aunque creo que la UI de Swing aún debería ser receptiva, no lo es. El progtwig de ejemplo a continuación muestra el problema: al hacer clic en el botón “Pruébeme” debería aparecer un cuadro de diálogo más o menos medio segundo más tarde, y debería ser posible cerrar ese cuadro de diálogo haciendo clic en cualquiera de sus respuestas. En cambio, el diálogo tarda mucho más en aparecer y / o tarda mucho tiempo en cerrarse después de hacer clic en uno de los botones.

  • El problema ocurre en Linux (dos máquinas diferentes con diferentes distribuciones), en Windows, en Raspberry Pi (solo VM del servidor) y en Mac OS X (informado por otro usuario de SO).
  • Java versión 1.8.0_65 y 1.8.0_72 (probado ambos)
  • procesador i7 con muchos núcleos. El EDT debe tener suficiente potencia de procesamiento disponible.

¿Alguien tiene alguna idea de por qué se está demorando el procesamiento de EDT, a pesar de que solo hay un hilo ocupado?

(Tenga en cuenta que a pesar de varias sugerencias de que la llamada Thread.sleep es la causa del problema, no lo es. Se puede eliminar y el problema aún se puede reproducir, aunque se manifiesta con menor frecuencia y generalmente muestra el segundo comportamiento descrito anteriormente – es decir JOptionPane diálogo de JOptionPane no receptivo en lugar de aparición de diálogo retardada Además, no hay ninguna razón por la que la llamada de reposo deba ceder al otro subproceso porque hay núcleos de procesador de repuesto como se mencionó anteriormente; el EDT podría continuar ejecutándose en otro núcleo después del llamar para 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); } } 

Actualización: el problema ocurre solo con la máquina virtual del servidor ( pero vea más actualizaciones ). Especificar la VM del cliente (argumento de la línea de comando del cliente al ejecutable de Java) parece suprimir el problema ( actualización 2 ) en una máquina pero no en otra .

Actualización 3: Veo el 200% de uso del procesador por el proceso de Java después de hacer clic en el botón, lo que implica que hay 2 núcleos de procesador cargados por completo. Esto no tiene sentido para mí en absoluto.

Actualización 4: también ocurre en Windows.

Actualización 5: el uso de un depurador (Eclipse) resulta problemático; el depurador parece incapaz de detener los hilos. Esto es muy inusual, y sospecho que hay algún tipo de condición de locking o carrera en vivo en la VM, así que he archivado un error con Oracle (revise la ID JI-9029194).

Actualización 6: encontré mi informe de errores en la base de datos de errores OpenJDK . (No me informaron que había sido aceptado, tuve que buscarlo). La discusión allí es muy interesante y ya arroja algo de luz sobre cuál puede ser la causa de este problema.

Veo el mismo efecto para Mac OS X. Aunque su ejemplo está sincronizado correctamente, la variabilidad plataforma / JVM que ve es probable debido a los caprichos en cómo se progtwign los hilos, lo que resulta en inanición. Agregar Thread.yield() al bucle externo en t mitiga el problema, como se muestra a continuación. Excepto por la naturaleza artificial del ejemplo, una sugerencia como Thread.yield() normalmente no se requeriría. En cualquier caso, considere SwingWorker , que se muestra aquí, ejecutando un ciclo similarmente estrecho para fines de demostración.

No creo que Thread.yield() deba llamarse en este caso, a pesar de la naturaleza artificial del caso de prueba.

Correcto; ceder simplemente expone el problema subyacente: t muere de hambre el hilo de envío del evento. Tenga en cuenta que la GUI se actualiza rápidamente en el ejemplo a continuación, incluso sin Thread.yield() . Como se discutió en este Q & A relacionado, puede intentar disminuir la prioridad del hilo. Alternativamente, ejecute t en una JVM separada, como se sugiere aquí usando ProcessBuilder , que también se puede ejecutar en el fondo de un SwingWorker , como se muestra aquí .

pero ¿por qué ?

Todas las plataformas compatibles se basan en bibliotecas de gráficos de subproceso único. Es bastante fácil bloquear, matar de hambre o saturar el hilo de envío de eventos de gobierno. Las tareas en segundo plano no triviales suelen generar implícitamente, como cuando se publican resultados intermedios, se bloquean las E / S o se espera una cola de trabajos. Una tarea que no funciona puede tener que ceder de forma explícita.

imagen

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

Mis observaciones:

  • Reemplace el hilo con swingworker:

    • Ninguna diferencia
  • Reemplace el hilo con el columpio y haga algo de trabajo dentro del primer ciclo:

    • Obtuve los resultados esperados de que la interfaz de usuario no se congelara y la navegación fuera suave desde aquí en adelante

Con este código, se observa el comportamiento esperado:

 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 misma observación se realiza mediante el uso del código original de OP:

  • Haz otro trabajo dentro del primer bucle
    • Obtuve resultados esperados

Ejemplo

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

Estoy ejecutando Ubuntu 14.04 en una máquina semi poderosa.

 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) 

¿Qué significan mis observaciones?

No se pierde mucho más que todo y alguien pudo haber optimizado demasiado el comstackdor, lo que hace que de alguna manera bloquee el hilo de la interfaz de usuario. Honestamente, no estoy seguro de lo que significa todo, pero estoy seguro de que alguien lo resolverá

  • por defecto, todo comenzó desde EDT (en este caso dentro del ActionListener ) bloqueado por Thread.sleep terminó cuando se ejecutó todo el código, incluido Thread.sleep , con el supuesto de que se han perdido todos los eventos, incluido. pintura, durante todo el tiempo cunsumed este código, todos los eventos están pintados al final y en un momento

  • este código perdió autoclose JOptionPane , nunca está pintado en la pantalla (simulación de cómo Hilo con comodidad. el Thread.sleep mata la pintura en Swing)

  • La GUI de Swing no es responsable del evento del mouse o tecla, no es posible finalizar esta aplicación, esto es posible solo desde Runnable#Thread y SwingWorker , eso está designado para comenzar nuevo, otro hilo ( Workers Thread ), durante cualquier cosa dentro de Runnable#Thread y SwingWorker es tarea cancelable (o usando Runnable#Thread es posible pausar, modificar …)

  • esto no se trata de multihilo, ni de compartir recursos con otro núcleo (s), en Win10 todos los núcleos me muestran, compartiendo el incremento proporcionalmente

salida del código (poco modificado) (basado en su 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) 

de nuevo autoclose JOptionPane nunca se pintará en la pantalla (probado win10-64b, i7, Java8), probablemente hasta en Java 1.6.022 todo estará pintado y correctamente (AFAIK es la última solución para edt y a partir de este momento SwingWorker funciona sin errores)

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

Tenga en cuenta que también debe probar con Runnable#Thread y SwingWorker

Esta no es una respuesta final, pero se acerca a la comprensión del problema.

Traté de minimizar el código para eliminar las posibles dificultades con el sleep y la actionPerformed y creo que lo he hecho manteniendo el problema intacto:

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

En Win7, i7 2670QM, JDK 1.8.0_25 obtengo los siguientes resultados:

Solo el 2do pack comentado:

 a b [pause] c-1863004573 

(Se espera que, incluso sin sincronización, se llegue a la impresión b antes de imprimir c , a menos que tal vez esté en algún procesador superduper que pueda hacer el cálculo más rápido).

Solo el primer pack comentado:

 a [pause] c-1863004573 b 

(no esperado)

¿Puedes confirmar mis resultados con / out el indicador de -client ?

Esto parece ser un problema. A continuación se muestra la observación Event Dispatcher Thread retrasa el procesamiento, debería haber respondido inmediatamente:

  1. Ejecutar progtwig de muestra
  2. Haz clic en el botón “Pruébame”
  3. Haga clic en cualquier botón (sí / no / cancelar) para cerrar el diálogo resultante

En Windows

Larga espera entre el paso 2 y el paso 3.

Paso 3 -> cierra el diálogo de inmediato.

En Linux

Paso 2 al paso 3: sin demoras

Paso 3 -> larga demora para cerrar el diálogo.