使用观察者模式的Java Swing多线程管理

时间:2017-03-16 18:03:29

标签: java multithreading swing observer-pattern swingworker

我的目标是使用Observer模式绘制一个矩形并从左向右平滑移动。

我有一个Model类,它是我放置矩形坐标的Observable,以及一个Display类,它是Observer,每当坐标在模型中发生变化时执行重绘。

Model中的坐标更改是在SwingWorker内部的while循环中进行的:在每次迭代时我将x坐标递增1,然后睡眠100 ms,然后通知Observer(Display)只执行任务一个重画。正如您所看到的那样,在EDT上调用repaint()方法就像建议的那样。

问题是大约一秒后移动不顺畅,重新显示频率变化,看起来矩形越来越少重新绘制。

这是Model类:

import java.util.Observable;
import java.awt.EventQueue;
import javax.swing.SwingWorker;

public class Model extends Observable{
    int xCoordinate;

    Model(Display d){
        SwingWorker<Void,Void> sw = new SwingWorker<Void,Void>(){

            @Override
            protected Void doInBackground() {
                while(xCoordinate<600){
                    xCoordinate ++;

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {}
                    setChanged();
                    notifyObservers(xCoordinate);
                }
                return null;
            }
        };
        addObserver(d);
        sw.execute();
    }

    public static void main(String[] a){
        EventQueue.invokeLater(new Runnable(){
            @Override
            public void run(){
                Display d = new Display();
                Model m = new Model(d);
                d.model = m;
            }
        });
    }
}

这是Display类:

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Display extends JFrame implements Observer{
    Model model;
    int xCoordinate;

    Display(){
        getContentPane().add(new JPanel(){
            @Override
            public void paintComponent(Graphics g){
                super.paintComponent(g);
                g.setColor(Color.RED);
                g.fillRect(xCoordinate, 1, 50, 50);
            }
        });
        setSize(600, 600);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    /* arg is the updated xCoordinate*/
    public void update(Observable o, Object arg) {
        xCoordinate = (Integer)arg;
        EventQueue.invokeLater(new Runnable(){
            @Override
            public void run() {
                repaint();
            }

        });
    }
}

我尝试了其他方法,例如在显示器中使用Timer,但这也没有用。 SwingWorker可能在这里没有用,因为在SwingWorker线程上进行的计算很容易(递增一个),但是我需要它用于我打算在我的项目(池游戏)上进行繁重的计算。

我也尝试通过查看两次重绘之间的时间(在显示中)和两次递增之间的时间(在模型中)进行调试,并且预期每次约100毫秒。

提前致谢

1 个答案:

答案 0 :(得分:1)

好的,作为初步测试,我开始使用Swing Timer ...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                Model model = new Model();

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane(model));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                Timer timer = new Timer(40, new ActionListener() {
                                                            @Override
                                                            public void actionPerformed(ActionEvent e) {
                                                                model.update();
                                                            }
                                                        });
                timer.setInitialDelay(1000);
                timer.start();
            }

        });
    }

    public class Model extends Observable {

        private int xCoordinate;

        public void update() {
            xCoordinate++;
            setChanged();
            notifyObservers(xCoordinate);
        }

        public int getXCoordinate() {
            return xCoordinate;
        }

    }

    public class TestPane extends JPanel implements Observer {

        private Model model;

        public TestPane(Model model) {
            this.model = model;
            model.addObserver(this);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g.setColor(Color.RED);
            g.fillRect(model.getXCoordinate(), 1, 50, 50);
            g2d.dispose();
        }

        @Override
        public void update(Observable o, Object arg) {
            System.out.println(arg);
            repaint();
        }

    }

}

我找到的是,你永远不会在setChanged上调用Observable,在我的测试中,这意味着它从未调用过Observer

我还用SwingWorker ...

进行了测试
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                Model model = new Model();

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane(model));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                SwingWorker worker = new SwingWorker() {
                    @Override
                    protected Object doInBackground() throws Exception {
                        Thread.sleep(1000);
                        while (true) {
                            model.update();
                        }
                    }
                };
                worker.execute();
            }

        });
    }

    public class Model extends Observable {

        private int xCoordinate;

        public synchronized void update() {
            xCoordinate++;
            setChanged();
            notifyObservers(xCoordinate);
        }

        public synchronized int getXCoordinate() {
            return xCoordinate;
        }

    }

    public class TestPane extends JPanel implements Observer {

        private Model model;

        public TestPane(Model model) {
            this.model = model;
            model.addObserver(this);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g.setColor(Color.RED);
            g.fillRect(model.getXCoordinate(), 1, 50, 50);
            g2d.dispose();
        }

        @Override
        public void update(Observable o, Object arg) {
            System.out.println(arg);
            repaint();
        }

    }

}

由于线程同步问题,我同步了对方法的访问,以确保值不会在更新之间发生变化。因为您正在使用Observer,实际上可以通过&#34;新状态&#34;至Observer,因此他们在使用模型时并不依赖于模型的值。

好的,所以它的长短不一,你需要在setChanged更新后致电Observable,以便notifyObservers实际上会调用Observer {1}}Š

正如有人毫无疑问地指出的那样,这种方法的时间不准确,其性质,Swing TimerThread.sleep只保证&#34;至少&#34 ;时间并可以在每次更新之间进行验证。

如果您有可变长度操作,这也会影响更新之间的时间。相反,您应该计算执行操作所花费的时间,减去您想要等待的时间,然后使用此延迟来计算您想要等待多长时间等待&#34;帧之间。您应该使用System.nanoTime而不是System.currentTimeMillis,因为它不会遇到相同的系统时钟同步问题