【问题标题】:Java Swing multithread management with observer pattern带有观察者模式的 Java Swing 多线程管理
【发布时间】:2017-08-08 01:47:23
【问题描述】:

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

我有一个 Model 类,它是 Observable,我在其中放置了矩形的坐标,还有一个 Display 类,它是 Observer,每次模型中的坐标发生变化时都会执行重绘。

Model 中的坐标更改是在 SwingWorker 内的 while 循环中进行的:在每次迭代中,我将 x 坐标加一,然后休眠 100 毫秒,然后通知观察者(显示器)唯一要执行的任务重绘。如您所见,在 EDT 上调用了 repaint() 方法,就像建议的那样。

问题是大约一秒钟后移动不顺畅,重绘频率发生变化,并且矩形似乎越来越少重绘。

这是模型类:

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

这里是显示类:

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

        });
    }
}

我尝试了其他方法,例如在 Display 中使用 Timer,但也没有用。 SwingWorker 在这里可能没用,因为在 SwingWorker 线程上进行的计算很容易(递增 1),但我需要它来处理我打算在我的项目(台球游戏)上进行的繁重计算。

我还尝试通过查看两次重绘之间的时间(在 Display 中)和两次增量之间的时间(在 Model 中)进行调试,结果与预期的一样,每次大约 100 毫秒。

提前致谢

【问题讨论】:

  • 你能改变睡眠时间吗?试试 10 毫秒,它会移动得更顺畅。
  • 它仍然不流畅(至少在我的电脑上)。我尝试了 10 和 500 毫秒。
  • 我尝试了一个使用javax.swing.Timer 的示例,但是将延迟设置为 100 毫秒它不会顺利移动。示例如下:java2s.com/Tutorial/Java/0240__Swing/Timerbasedanimation.htm.
  • 请注意Thread.sleep(long milis) 受制于系统计时器和调度程序的精度和准确性。它可能并不总是在给定的睡眠时间完全入睡,可能需要更长的时间。
  • 我会因工作人员/观察者和绘制程序之间的同步问题而受到保护

标签: java multithreading swing observer-pattern swingworker


【解决方案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();
        }

    }

}

我发现,你从来没有在Observable 上调用过setChanged,这在我的测试中意味着它从来没有调用过Observers

我还用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,实际上可以将“新状态”传递给Observer,因此它们在使用它们时并不依赖模型的值。

好的,总而言之,更新后您需要在Observable 上调用setChanged,以便notifyObservers 实际调用Observers

正如有人无疑指出的那样,这种方法存在时间不准确的问题,就其本质而言,Swing TimerThread.sleep 都只能保证“至少”时间,并且可能会在每次更新之间进行验证。

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

【讨论】:

  • 好的,谢谢你的帮助,我想我明白你的意思了,但是当我将 Thread.sleep(100) 放入 while(true) 循环时,我的电脑上的动画仍然不流畅SwingWorker(在你的第二个例子中)。所以问题来自 Thread.sleep() 的不准确性?我该如何处理呢?因为即使你的答案是计算我想“等待”多长时间,我也必须使用 Thread.sleep 对吗?
  • 不,我想说这归结为仅以 10fps (1000/100) 运行(并且同步很慢),我会考虑减少延迟(40 是25fps,16 是 60fps),看看它有什么不同。根据您要执行的操作,基于时间的方法可能更合适。也就是说,给定一定的时间,将对象移动一定距离,如果操作正确,算法可以“丢弃”帧,从而产生更好的运动错觉
猜你喜欢
  • 2013-03-05
  • 2012-12-29
  • 2013-12-02
  • 2012-09-02
  • 2012-02-03
  • 2013-11-05
  • 2023-03-30
  • 1970-01-01
  • 2012-01-22
相关资源
最近更新 更多