2012-12-25 9 views
11

La mia comprensione: A differenza della maggior parte dei componenti/operazioni nella chiamata Swing a JComponent.repaint() è thread-safe, ad esempio se una richiesta di ridisegno è fatta da un altro thread (cioè non da EDT), la pittura effettiva avviene in Solo EDT. Sotto lo snippet di codice lo dimostra.Come funziona JComponent.paintImmediately() in Java Swing?

public class PaintingDemo { 

    public static void main(String[] args) { 
     final JFrame frame = new JFrame(); 
     final JPanel p = new MyPanel(); 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       frame.add(p, BorderLayout.CENTER); 
       frame.setSize(200, 200); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.setVisible(true); 
      } 
     }); 
     new Thread("MyThread") { 
      public void run() { 
       while (true) { 
       // Below statements are important to show the difference 
        p.repaint(); 
        p.paintImmediately(p.getBounds()); 
        try { 
         Thread.sleep(1000); 
        } catch(Exception e) {} 
       } 
      } 
     }.start(); 
    } 

} 

class MyPanel extends JPanel { 
    @Override 
    public void paint(Graphics g) { 
     System.out.println("paint() called in "+ Thread.currentThread().getName()); 
     super.paint(g); 
    } 
} 

Dall'output si sa che la pittura è fatto in EDT quando repaint() viene richiamato a prescindere da quale thread viene chiamato - quindi nessun problema. Ma, nel caso di paintImmediately() - la pittura avviene nello stesso thread da cui è chiamata.

Considerare un caso in cui EDT sta cambiando lo stato di un componente e un altro thread (da cui viene richiamato paintImmediately()) sta dipingendo lo stesso componente.

La mia domanda: In caso di paintImmediately(), come è la sincronizzazione tra Event Dispatcher Discussione (EDT) e altri thread gestito?

risposta

5

Per la mia comprensione, quando si chiama paintImmediately, si chiama il seguente codice:

 Component c = this; 
     Component parent; 

     if(!isShowing()) { 
      return; 
     } 

     JComponent paintingOigin = SwingUtilities.getPaintingOrigin(this); 
     if (paintingOigin != null) { 
      Rectangle rectangle = SwingUtilities.convertRectangle(
        c, new Rectangle(x, y, w, h), paintingOigin); 
      paintingOigin.paintImmediately(rectangle.x, rectangle.y, rectangle.width, rectangle.height); 
      return; 
     } 

     while(!c.isOpaque()) { 
      parent = c.getParent(); 
      if(parent != null) { 
       x += c.getX(); 
       y += c.getY(); 
       c = parent; 
      } else { 
       break; 
      } 

      if(!(c instanceof JComponent)) { 
       break; 
      } 
    } 
    if(c instanceof JComponent) { 
     ((JComponent)c)._paintImmediately(x,y,w,h); 
    } else { 
     c.repaint(x,y,w,h); 
    } 

Quindi, a meno che questa non è una JComponent, si finisce per chiamare _paintImmediately() che finisce per chiamare paint(Graphics) come suggerisce l'analisi dello stack qui di seguito (catturato da un pezzo di codice mi post alla fine di questo post):

Thread [pool-1-thread-1] (Suspended)  
    TestPaint$1.paint(Graphics) line: 23  
    TestPaint$1(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5221 
    RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1482 
    RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1413 
    RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1206 
    TestPaint$1(JComponent)._paintImmediately(int, int, int, int) line: 5169  
    TestPaint$1(JComponent).paintImmediately(int, int, int, int) line: 4980 
    TestPaint$1(JComponent).paintImmediately(Rectangle) line: 4992 
    TestPaint$3.run() line: 50 
    ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1110 
    ThreadPoolExecutor$Worker.run() line: 603 
    Thread.run() line: 722 

Ma se si tenta di chiamare anche repaint() contemporaneamente (da un altro thread), si vede che entrambi eseguito allo stesso tempo (ho provato a inserire il codice con il debugger e la pittura non si è mai interrotta nell'altro thread), sembra che a livello di codice Java non ci sia molta sincronizzazione (almeno non ho potuto individuare nulla). Quindi, se si finisce per modificare lo stato del componente nell'EDT, credo che i risultati siano alquanto imprevedibili e si dovrebbe evitare tale situazione con tutti i mezzi.

solo illustrare il mio punto, ho provato modificare lo stato di una variabile all'interno del metodo paint, aggiungere un sleep per aumentare il rischio di collisione tra le 2 Fili (EDT e l'altra) e sembra obvisouly che non c'è sincronizzazione tra i due thread (il numero System.err.println() ha emesso il numero null di volta in volta).

Ora mi chiedo perché è necessario eseguire una verniciatura immediatamente. A meno che non si stia bloccando l'EDT, non ci sono molti validi motivi per eseguire tale operazione.

Di seguito è riportato il codice che ho utilizzato per testare quelle cose (molto vicino a quello pubblicato nella domanda). Il codice è pensato solo per cercare di capire cosa sta succedendo, non per mostrare come eseguire la pittura appropriata né alcuna buona pratica Swing.

import java.awt.Color; 
import java.awt.Graphics; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.Random; 
import java.util.concurrent.Executors; 

import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 
import javax.swing.Timer; 

public class TestPaint { 

    protected void initUI() { 
     JFrame frame = new JFrame(); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setTitle(TestPaint.class.getSimpleName()); 
     final Random rand = new Random(); 
     final JPanel comp = new JPanel() { 
      private String value; 

      @Override 
      public void paint(Graphics g) { 
       value = "hello"; 
       super.paint(g); 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
       g.setColor(new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256))); 
       g.fillRect(0, 0, getWidth(), getHeight()); 
       if (SwingUtilities.isEventDispatchThread()) { 
        System.err.println("Painting in the EDT " + getValue()); 
       } else { 
        System.err.println("Not painting in EDT " + getValue()); 
       } 
       value = null; 
      } 

      public String getValue() { 
       return value; 
      } 
     }; 
     frame.add(comp); 
     frame.setSize(400, 400); 
     frame.setLocationRelativeTo(null); 
     frame.setVisible(true); 
     Timer t = new Timer(1, new ActionListener() { 

      @Override 
      public void actionPerformed(ActionEvent e) { 
       comp.repaint(); 
      } 
     }); 
     t.start(); 
     Executors.newSingleThreadExecutor().execute(new Runnable() { 

      @Override 
      public void run() { 
       while (true) { 
        comp.paintImmediately(comp.getBounds()); 
       } 
      } 
     }); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new TestPaint().initUI(); 
      } 
     }); 
    } 

} 
+0

+1 lo hai inchiodato. Mi chiedo solo perché i documenti 'JComponent' fanno riferimento a' paintImmediately() 'e' repaint (..) 'dove la fonte è diversa. Devo però chiedere nel tuo frammento di non sovrascrivere 'paint (..)' di un 'JComponent', quindi perché non hai usato' paintComponent' (sembra ancora riprodurre i risultati)? E un'altra domanda perché aumentare il "sonno (int milis)" non aumenta le collisioni? E anche perché chiami 'paintImmediately' da un thread diverso da EDT, (perché vedo una volta chiamato EDT non si verificherà nulla)? –

+0

Grazie a @Guillaume per provarlo tu stesso e condividere la tua ricerca.Sono d'accordo che non ci sono molti motivi per usare paintImmediately() ma stavo solo cercando di capire "come funziona?" Perché javadoc dice ... "Questo metodo è utile se è necessario aggiornare il display mentre viene inviato l'evento corrente." Inoltre, questo metodo è pubblico e non vi sono assolutamente avvertenze menzionate in alcuno dei suoi avvertimenti; almeno il javadoc è fuorviante. Ora capisco che paintImmediately() deve essere chiamato all'interno di EDT e non è thread-safe a differenza di repaint(). – Learner

+0

@DavidKroukamp Assolutamente nessuna buona ragione per sovrascrivere 'paint' piuttosto che' paintComponent', ho solo pensato che nel tentativo di vedere come 'paintImmediately' alla fine richiama' paint' aggiungendo la chiamata aggiuntiva a 'paintComponent' nella chiamata stack non era necessaria e i risultati osservati rimarrebbero gli stessi. Quindi era più semplicemente rimuovere ogni "rumore" dalle informazioni, e come ho detto prima del mio frammento non illustra il modo corretto di usare Swing. Saluti ;-) –