11

私の理解: JComponent.repaint() への Swing 呼び出しのほとんどのコンポーネント/操作とは異なり、スレッドセーフです。つまり、再描画要求が別のスレッドから (つまり、EDT からではなく) 行われますが、実際の描画は EDT でのみ行われます。以下のコード スニペットはこれを示しています。

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

出力から、どのスレッドから呼び出されたかに関係なく repaint() が呼び出されたときに EDT で描画が行われることがわかっているため、問題はありません。ただし、paintImmediately() の場合、ペイントは呼び出し元と同じスレッドで行われます。

EDT がコンポーネントの状態を変更し、別のスレッド (そこから paintImmediately() が呼び出される) が同じコンポーネントをペイントしている場合を考えてみましょう。

私の質問: paintImmediately() の場合、イベント ディスパッチャー スレッド (EDT) と他のスレッド間の同期はどのように処理されますか?

4

1 に答える 1

7

私の理解では、paintImmediately を呼び出すときは、次のコードを呼び出します。

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

したがって、これが でない限り、以下のスタック トレースが示唆するようにJComponent呼び出し_paintImmediately()が終了しpaint(Graphics)ます (この投稿の最後に投稿するコードの一部からキャプチャされます)。

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  

しかし、repaint()(別のスレッドから) 同時に呼び出しようとすると、両方が同時に実行されることがわかります (デバッガーを使用してコードにステップインしようとしましたが、他のスレッドでペイントが停止することはありませんでした)。Java コード レベルでは、同期はあまりありません(少なくとも何も見つけることができませんでした)。したがって、EDT でコンポーネントの状態を変更してしまうと、結果が予測しにくくなると思いますので、そのような状況は絶対に避けてください。

私の要点を説明するために、メソッド内の変数の状態を変更しようとしました。2 つのスレッド (EDT とその他) 間の衝突のリスクを高めるためpaintに a を追加しましたが、明らかに 2 つのスレッド間に同期がないように見えます (随時出力さsleepれます)。System.err.println()null

ここで、なぜすぐにペイントを実行する必要があるのか​​ 疑問に思います。EDT をブロックしていない限り、そのようなことを実行する正当な理由はあまりありません。

以下は、それらをテストするために使用したコードです(質問に投稿されたものにかなり近い)。このコードは、何が起こっているのかを理解しようとすることだけを目的としており、適切な描画や適切な 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();
            }
        });
    }

}
于 2012-12-25T22:00:23.607 に答える