2012-12-25 14 views
11

Moje zrozumienie: W przeciwieństwie do większości komponentów/operacji w wywołaniu Swinga do JComponent.repaint() jest bezpieczne dla wątków, tj. Mimo że żądanie ponownego malowania jest wykonane z innego wątku (tj. Nie z EDT), rzeczywiste malowanie odbywa się w Tylko EDT. Poniższy fragment kodu demonstruje to.Jak działa funkcja JComponent.paintImmediately() w 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); 
    } 
} 

Z wyjścia wiadomo, że malowanie odbywa się w EDT kiedy repaint() jest wywoływana bez względu z której wątek nazywa się - więc nie ma problemów. Ale w przypadku paintImmediately() - malowanie odbywa się w tym samym wątku, z którego się nazywa.

Rozważmy przypadek, w którym EDT zmienia stan komponentu, a inny wątek (z którego wywoływany jest paintImmediately()) maluje ten sam komponent.

Moje pytanie: W przypadku paintImmediately(), jak jest synchronizacja pomiędzy wątek Event Dispatcher (EDT) i innych wątków obchodzić?

Odpowiedz

5

mojego rozeznania, gdy dzwonisz paintImmediately, wywołać następujący kod:

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

Więc, chyba że nie jest to JComponent, skończyć się nazywając _paintImmediately() który kończy się wywołaniem paint(Graphics) jak sugeruje ślad stosu poniżej (zrobione z kawałka kodu będę pisać na końcu tego postu):

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 

Ale jeśli spróbujesz również zadzwonić repaint() jednocześnie (z innego wątku), widać, że zarówno uruchomione w tym samym czasie (próbowałem wkleić kod z debugerem i malowanie nigdy nie przestało występować w drugim wątku) wydaje się, że na poziomie kodu Java nie ma dużej synchronizacji (przynajmniej nie mogłem niczego wykryć). Jeśli więc zmienisz stan komponentu w EDT, uważam, że wyniki są dość nieprzewidywalne i powinieneś unikać takiej sytuacji za wszelką cenę.

Żeby zilustrować mój punkt widzenia, próbowałem modyfikując stan zmiennej w metodzie paint dodaj sleep zwiększa ryzyko kolizji między 2 Nitki (EDT i drugi) i obvisouly wydaje się, że nie ma synchronizacja między dwoma wątkami (System.err.println() wyprowadzana od czasu do czasu).

Teraz zastanawiałam się, dlaczego musisz wykonać farbę od razu. O ile nie blokujesz EDT, nie ma aż tak wielu ważnych powodów, aby to zrobić.

Poniżej znajduje się kod użyty do przetestowania tych rzeczy (całkiem blisko tego, który zamieszczono w pytaniu). Kod ma na celu jedynie zrozumienie tego, co się dzieje, a nie pokazanie, jak wykonać właściwe malowanie, ani jakiejkolwiek dobrej praktyki Swinga.

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 przygwoździliście to. Zastanawiam się tylko, dlaczego 'dokumentacja JComponent' odwołuje się do' paintImmediately() 'i' repaint (..) 'gdzie jako źródło jest inne. Muszę jednak zapytać, czy w twoim urywku nigdy byśmy nie nadpisali 'paint (..)' 'JComponent ', dlaczego więc nie użyłeś' paintComponent' (nadal wydaje się odtwarzać wyniki)? I jeszcze jedno pytanie, dlaczego zwiększenie "snu (int milis)" nie zwiększy kolizji? A także dlaczego nazywacie 'paintImmediately' z wątku innego niż EDT (ponieważ widzę raz wywołany na EDT nie pojawi się null)? –

+0

Dzięki @Guillaume za wypróbowanie tego i podzielenie się swoimi badaniami.Zgadzam się, że nie ma wielu powodów, aby używać paintImmediately(), ale próbowałem zrozumieć "jak to działa?" Ponieważ javadoc mówi ... "Ta metoda jest przydatna, jeśli trzeba zaktualizować wyświetlacz podczas wysyłania bieżącego zdarzenia." Ta metoda jest również publiczna i nie ma absolutnie żadnych ostrzeżeń nigdzie wspomnianych o jej zastrzeżeniach; przynajmniej javadoc wprowadza w błąd. Teraz rozumiem paintImmediately() musi być wywoływany w EDT i nie jest bezpieczny dla wątków w przeciwieństwie do repaint(). – Learner

+0

@DavidKroukamp Absolutnie nie ma żadnego powodu, aby przesłonić 'paint' zamiast' paintComponent', po prostu myślałem, że przy próbie zobaczenia jak 'paintImmediately' ostatecznie wywołuje' paint' dodanie dodatkowego wywołania 'paintComponent' w wywołaniu stosu było niepotrzebne a zaobserwowane wyniki pozostaną takie same. Tak więc było po prostu usunięcie wszelkich "szumów" z informacji, a jak powiedziałem przed moim urywkiem, nie ilustruje to właściwego sposobu użycia Swinga. Twoje zdrowie ;-) –