2008-10-08 32 views
22

A menudo escuché críticas sobre la falta de seguridad de subprocesos en las bibliotecas Swing. Sin embargo, no estoy seguro de lo que haría en mi propio código con podría causar problemas:Java: Bibliotecas Swing y seguridad de subprocesos

¿En qué situaciones entra en juego el hecho de que Swing no es seguro para hilos?

¿Qué debo evitar activamente?

Respuesta

26
  1. Nunca realice tareas largas en respuesta a un botón, evento, etc. ya que están en la cadena de eventos. Si bloquea el hilo del evento, la GUI ENTERA no responderá por completo, lo que resultará en usuarios MUY cabreados. Es por eso que Swing parece lento y crujiente.

  2. Use los subprocesos, los ejecutores y SwingWorker para ejecutar tareas NO EN EL EDT (subproceso de envío de eventos).

  3. No actualice ni cree widgets fuera del EDT. Casi la única llamada que puede hacer fuera del EDT es Component.repaint(). Use SwingUtilitis.invokeLater para asegurarse de que se ejecute cierto código en el EDT.

  4. Uso EDT Debug Techniques y un aspecto elegante y la sensación (como Substance, que comprueba EDT violación)

Si sigue estas reglas, Swing puede hacer algo muy atractivo y GUIs RESPONSABLES

Un ejemplo de algunos REALMENTE increíbles UI Swing trabajo: Palantir Technologies. Nota: NO trabajo para ellos, solo un ejemplo de increíble swing. No se avergüence de ninguna demostración pública ... Su blog es bueno también, escaso, pero bueno

+1

También hay un método invokeAndWait(), pero use invokeLater() siempre que sea posible. – Powerlord

+1

Use invokeAndWait si desea puntos muertos. ;) –

+0

O se pagan por hora. :-) –

3

Evite realizar cualquier trabajo de oscilación, excepto en el envío de eventos. Swing fue escrito para ser fácil de extender y Sun decidió que un modelo de un solo hilo era mejor para esto.

No he tenido problemas mientras sigo mi consejo anterior. Hay algunas circunstancias en las que puede 'cambiar' de otros hilos pero nunca he encontrado la necesidad.

8

No es solo que Swing no es seguro para subprocesos (no mucho), pero es hostil a los hilos. Si comienza a hacer cosas de Swing en un solo hilo (que no sea el EDT), entonces cuando en los casos en que Swing cambie al EDT (no documentado) puede haber problemas de seguridad de hilos. Incluso el texto Swing, que apunta a ser seguro para subprocesos, no es útil para subprocesos (por ejemplo, para anexar a un documento, primero debe encontrar la longitud, que puede cambiar antes de la inserción).

Por lo tanto, haga todas las manipulaciones de Swing en el EDT. Tenga en cuenta la EDT no es el hilo principal se llama en adelante, a fin de comenzar sus aplicaciones (sencilla) de Swing como esto repetitivo:

class MyApp { 
    public static void main(String[] args) { 
     java.awt.EventQueue.invokeLater(new Runnable() { public void run() { 
      runEDT(); 
     }}); 
    } 
    private static void runEDT() { 
     assert java.awt.EventQueue.isDispatchThread(); 
     ... 
+0

+1 para thread-hostile – Fortega

2

Si está utilizando Java 6 a continuación SwingWorker es sin duda la forma más fácil para hacer frente a este .

Básicamente, quiere asegurarse de que todo lo que cambie una IU se realice en el EventDispatchThread.

Esto se puede encontrar utilizando el método SwingUtilities.isEventDispatchThread() para decirle si se encuentra en él (generalmente no es una buena idea, debe saber qué hilo está activo).

Si no está en el EDT, usa SwingUtilities.invokeLater() y SwingUtilities.invokeAndWait() para invocar un Runnable en el EDT.

Si actualiza la IU que no está en el EDT, obtendrá un comportamiento increíblemente extraño. Personalmente, no considero que esto sea una falla de Swing, obtienes una buena eficacia al no tener que sincronizar todos los hilos para proporcionar una actualización de UI, solo necesitas recordar esa advertencia.

11

Esta es una de esas preguntas que me alegra de haber comprado Robinson & Vorobiev's book on Swing.

Cualquier cosa que accede al estado de un java.awt.Component se debe ejecutar dentro de la EDT, con tres excepciones: cualquier cosa documentado específicamente como hilo de seguridad, tales como repaint(), revalidate(), y invalidate(); cualquier componente en una interfaz de usuario que aún no haya sido realizado; y cualquier Componente en un Applet antes de que se haya llamado al start() de Applet.

Los métodos especialmente fabricados a prueba de hilos son tan poco comunes que a menudo es suficiente simplemente recordar los que sí lo son; generalmente también puede salirse con la suya asumiendo que no existen tales métodos (es perfectamente seguro envolver una llamada de repintado en un SwingWorker, por ejemplo).

Realizado significa que el componente es o bien un contenedor de nivel superior (como JFrame) en el que cualquiera de setVisible(true), show(), o pack() que se ha llamado, o se ha añadido a un componente dado cuenta. Esto significa que está perfectamente bien construir su UI en el método main(), como lo hacen muchos ejemplos de tutoriales, ya que no llaman al setVisible(true) en el contenedor de nivel superior hasta que se hayan agregado todos los Componente, fuentes y bordes configurados, etc.

Por razones similares, es perfectamente seguro construir la interfaz de usuario de su applet en su método init(), y luego llamar al start() después de que esté todo construido.

Envoltura posterior Los cambios de componentes en Runnables para enviar a invokeLater() se vuelven fáciles de conseguir después de hacerlo solo unas pocas veces. Lo único que me molesta es leer el estado de un Componente (por ejemplo, someTextField.getText()) desde otro subproceso. Técnicamente, esto tiene que ser envuelto en invokeLater(), también; en la práctica, puede hacer que el código sea feo y rápido, y con frecuencia no me molesto, o tengo cuidado de obtener esa información en el momento inicial del manejo del evento (normalmente es el momento adecuado para hacerlo en la mayoría de los casos).

+2

"Componente en una interfaz de usuario que aún no se ha realizado": ya no es una práctica recomendada. Siempre debe trabajar con sus componentes en EDT, independientemente de si se realizan o no. –

+1

Asumiendo que tienes razón, me pregunto por qué lo recomendarían. Es un cambio sustancial, dado que afectaría grandes cantidades de código de tutorial. ... De hecho, no puedo encontrar tutoriales de Java 6 que hagan esta recomendación; El código de muestra sigue construyendo UI en el hilo principal. ¿Tienes una fuente para esto? –

+1

@Paul 'tis true ver http://stackoverflow.com/questions/491323/is-it-safe-to-construct-swing-awt-widgets-not-on-the-event-dispatch-thread/491377#491377 y más. Es retrospectivo, y es un dolor. – Pool

1

Aquí hay un patrón para hacer swing swing-freindly.

Subclase Acción (MiAcción) y hazlo doAction roscado. Haga que el constructor tome un NOMBRE de cadena.

Dale un método abstracto actionImpl().

Vamos a ver como .. (pseudocódigo advertencia!)

doAction(){ 
new Thread(){ 
    public void run(){ 
    //kick off thread to do actionImpl(). 
     actionImpl(); 
     MyAction.this.interrupt(); 
    }.start(); // use a worker pool if you care about garbage. 
try { 
sleep(300); 
Go to a busy cursor 
sleep(600); 
Show a busy dialog(Name) // name comes in handy here 
} catch(interrupted exception){ 
    show normal cursor 
} 

Puede grabar el tiempo necesario para la tarea, y la próxima vez, el diálogo puede mostrar una estimación decente.

Si quieres ser realmente agradable, también duerme en otro hilo de trabajo.

2

La frase 'hilo-inseguro' parece que hay algo inherentemente malo (ya sabes ... 'seguro' - bueno, 'inseguro' - malo). La realidad es que la seguridad de los hilos tiene un costo: los objetos ensartables a menudo son mucho más complejos de implementar (y Swing es lo suficientemente complejo incluso como lo es).

Además, la seguridad de los hilos se logra mediante el bloqueo (lento) o estrategias de comparación-y-intercambio (complejas). Dado que la GUI interactúa con humanos, que tienden a ser impredecibles y difíciles de sincronizar, muchos kits de herramientas han decidido canalizar todos los eventos a través de una única bomba de evento. Esto es cierto para Windows, Swing, SWT, GTK y probablemente otros. En realidad, no conozco un solo juego de herramientas GUI que sea realmente seguro para subprocesos (lo que significa que puede manipular el estado interno de sus objetos desde cualquier hilo).

Lo que generalmente se hace en su lugar es que las GUI proporcionan una forma de hacer frente a la inseguridad del hilo. Como señalaron otros, Swing siempre ha proporcionado SwingUtilities.invokeLater() algo simplista. Java 6 incluye el excelente SwingWorker (disponible para versiones anteriores de Swinglabs.org). También hay bibliotecas de terceros como Foxtrot para gestionar los hilos en el contexto Swing.

La notoriedad de Swing se debe a que los diseñadores han tomado la postura liviana de asumir que el desarrollador hará lo correcto y no parará el EDT ni modificará los componentes desde fuera del EDT. Han declarado su política de enhebrado alto y claro y depende de los desarrolladores seguirla.

Es trivial hacer que cada API oscilante publique un trabajo en el EDT para cada conjunto de propiedades, invalidar, etc., lo que lo haría seguro para la tarea, pero a costa de desaceleraciones masivas. Incluso puedes hacerlo tú mismo usando AOP. A modo de comparación, SWT arroja excepciones cuando se accede a un componente desde un hilo equivocado.

1

Tenga en cuenta que ni siquiera las interfaces del modelo son seguras para hilos. El tamaño y el contenido se consultan con métodos de obtención separados, por lo que no hay forma de sincronizarlos.

Al actualizar el estado del modelo desde otro subproceso, al menos se puede pintar una situación donde el tamaño es aún mayor (la fila de la tabla sigue en su lugar), pero el contenido ya no está allí.

Actualizar el estado del modelo siempre en EDT evita esto.

1

invokeLater() y invokeAndWait() realmente DEBEN utilizarse cuando se realiza cualquier interacción con componentes de la GUI desde cualquier hilo que NO sea el EDT.

Puede funcionar durante el desarrollo, pero como la mayoría de los errores concurrentes, comenzará a aparecer excepciones extrañas que parecen completamente no relacionadas, y ocurren de manera no determinante, generalmente detectadas DESPUÉS de que los haya enviado un usuario real. No está bien.

Además, no tiene confianza de que su aplicación continuará funcionando en futuras CPU con más y más núcleos, que son más propensos a problemas de subprocesamiento debido a que son realmente concurrentes en lugar de simplemente simulados por el sistema operativo .

Sí, se pone feo envolviendo todas las llamadas de método en el EDT en una instancia de Runnable, pero eso es Java para usted. Hasta que tengamos cierres, solo tienes que vivir con eso.

4

Una alternativa al uso de pieles inteligentes como la sustancia es crear el siguiente método de utilidad:

public final static void checkOnEventDispatchThread() { 
    if (!SwingUtilities.isEventDispatchThread()) { 
     throw new RuntimeException("This method can only be run on the EDT"); 
    } 
} 

de llamadas en cada método que escribir lo que se requiere para estar en el hilo de eventos de despacho. Una ventaja de esto sería deshabilitar y habilitar comprobaciones de todo el sistema muy rápidamente, por ejemplo, posiblemente eliminar esto en producción.

Nota: las máscaras inteligentes pueden, por supuesto, proporcionar cobertura adicional y solo esto.

Cuestiones relacionadas