2009-09-04 4 views
9

La mia strategia per problemi di threading in un'applicazione Java Swing è quella di dividere i metodi in tre tipi:Java: il test di accesso thread per "non thread-safe" metodi

  1. metodi che dovrebbero essere accessibili dal filo GUI. Questi metodi non dovrebbero mai bloccare e possono chiamare metodi swing. Non thread-safe.
  2. metodi a cui è possibile accedere da thread non GUI. Fondamentalmente questo vale per tutte le (potenzialmente) operazioni di blocco come disco, database e accesso alla rete. Non dovrebbero mai chiamare metodi swing. Non thread-safe.
  3. metodi a cui è possibile accedere da entrambi. Questi metodi devono essere thread-safe (ad esempio sincronizzati)

Penso che questo sia un approccio valido per le applicazioni GUI, dove di solito ci sono solo due thread. Tagliare il problema aiuta davvero a ridurre la "superficie" per le condizioni di gara. Il caveat, naturalmente, è che non si chiama mai accidentalmente un metodo dal thread sbagliato.

La mia domanda riguarda il test:

Ci sono strumenti di test che possono aiutare me check che un metodo viene chiamato dal thread giusto? So di SwingUtilities.isEventDispatchThread(), ma sto davvero cercando qualcosa usando le annotazioni Java o la programmazione orientata all'aspetto in modo che non debba inserire lo stesso codice boilerplate in ogni singolo metodo del programma.

+0

+1 per la domanda di creatività – KLE

+0

sincronizzata non è uguale a thread safe. Vi consiglio caldamente di leggere le "nuove" librerie di concorrenza in java 5, specialmente i Futures sembrano essere utili per lo sviluppo di Swing, credo. –

+0

@Jens: hai ragione, ho modificato leggermente la domanda. – amarillion

risposta

2

Grazie per tutti i suggerimenti, ecco la soluzione che ho trovato alla fine. E 'stato più facile di quanto pensassi. Questa soluzione utilizza sia AspectJ che annotazioni. Funziona così: basta aggiungere una delle annotazioni (definite di seguito) a un metodo o una classe e all'inizio verrà inserito un semplice controllo per le violazioni delle regole EDT. Soprattutto se contrassegni intere classi come questa, puoi fare un sacco di test con solo una piccola quantità di codice extra.

Per prima cosa ho scaricato AspectJ e ha aggiunto al mio progetto (in Eclipse è possibile utilizzare AJDT)

Poi ho definito due nuovi NOTE:

import java.lang.annotation.ElementType; 
import java.lang.annotation.Target; 

/** 
* Indicates that this class or method should only be accessed by threads 
* other than the Event Dispatch Thread 
* <p> 
* Add this annotation to methods that perform potentially blocking operations, 
* such as disk, network or database access. 
*/ 
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR}) 
public @interface WorkerThreadOnly {} 

e

import java.lang.annotation.ElementType; 
import java.lang.annotation.Target; 

/** 
* Indicates that this class or method should only be accessed by the 
* Event Dispatch Thread 
* <p> 
* Add this annotation to methods that call (swing) GUI methods 
*/ 
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR}) 
public @interface EventDispatchThreadOnly {} 

Dopo di che , Ho definito l'Aspetto che esegue il controllo effettivo:

import javax.swing.SwingUtilities; 

/** Check methods/classes marked as WorkerThreadOnly or EventDispatchThreadOnly */ 
public aspect ThreadChecking { 

    /** you can adjust selection to a subset of methods/classes */ 
    pointcut selection() : execution (* *(..)); 

    pointcut edt() : selection() && 
     (within (@EventDispatchThreadOnly *) || 
     @annotation(EventDispatchThreadOnly)); 

    pointcut worker() : selection() && 
     (within (@WorkerThreadOnly *) || 
     @annotation(WorkerThreadOnly)); 

    before(): edt() { 
     assert (SwingUtilities.isEventDispatchThread()); 
    } 

    before(): worker() { 
     assert (!SwingUtilities.isEventDispatchThread()); 
    } 
} 

Ora aggiungi @EventDispatchThreadOnly o @WorkerThreadOnly ai metodi o alle classi che devono essere confinati in thread. Non aggiungere nulla ai metodi thread safe.

Infine, esegui semplicemente con le asserzioni abilitate (opzione JVM -ea) e scoprirai presto le eventuali violazioni.

Per gli scopi di riferimento, ecco la soluzione di Alexander Potochkin, a cui Mark si è riferito. È un approccio simile, ma controlla le chiamate ai metodi swing dalla tua app, anziché le chiamate all'interno della tua app. Entrambi gli approcci sono complementari e possono essere utilizzati insieme.

import javax.swing.*; 

aspect EdtRuleChecker { 
    private boolean isStressChecking = true; 

    public pointcut anySwingMethods(JComponent c): 
     target(c) && call(* *(..)); 

    public pointcut threadSafeMethods():   
     call(* repaint(..)) || 
     call(* revalidate()) || 
     call(* invalidate()) || 
     call(* getListeners(..)) || 
     call(* add*Listener(..)) || 
     call(* remove*Listener(..)); 

    //calls of any JComponent method, including subclasses 
    before(JComponent c): anySwingMethods(c) && 
          !threadSafeMethods() && 
          !within(EdtRuleChecker) { 
    if(!SwingUtilities.isEventDispatchThread() && 
     (isStressChecking || c.isShowing())) 
    { 
      System.err.println(thisJoinPoint.getSourceLocation()); 
      System.err.println(thisJoinPoint.getSignature()); 
      System.err.println(); 
     } 
    } 

    //calls of any JComponent constructor, including subclasses 
    before(): call(JComponent+.new(..)) { 
     if (isStressChecking && !SwingUtilities.isEventDispatchThread()) { 
      System.err.println(thisJoinPoint.getSourceLocation()); 
      System.err.println(thisJoinPoint.getSignature() + 
           " *constructor*"); 
      System.err.println(); 
     } 
    } 
} 
1

Da quello che ho letto, hai già una soluzione specifica, vuoi solo ridurre il codice boilerplate che è richiesto.

Vorrei utilizzare una tecnologia di intercettazione.

I nostri progetti utilizzano Spring e creiamo facilmente un Interceptor che controlla questa condizione prima di ogni chiamata. Durante il solo testing, utilizzeremmo una configurazione Spring che crea un intercettore (riusciamo a riutilizzare la normale configurazione di Spring, basta aggiungerla).

Per sapere quale caso dovremmo utilizzare per un metodo, è possibile leggere l'annotazione per esempio oppure utilizzare un'altra media di configurazione.

2

Here è un post di blog con alcune soluzioni per il controllo delle violazioni EDT. Uno è un gestore ridipingere personalizzato e c'è anche una soluzione AspectJ. Ho usato il gestore ridipingere in passato e l'ho trovato abbastanza utile.

1

Di gran lunga la cosa più importante da fare è garantire una chiara separazione tra EDT e non EDT. Metti una chiara interfaccia tra i due. Non ho classi (SwingWorker, ti sto guardando) con metodi in entrambi i campi. Questo vale per i thread in generale. Il dispari assert java.awt.EventQueue.isDispatchThread(); è bello vicino alle interfacce tra i thread, ma non rimanere appeso su di esso.