Java缓冲区策略导致严重滞后

Java缓冲区策略导致严重滞后,java,java-2d,Java,Java 2d,我有一个小引擎,在我的OSX笔记本电脑上运行得很好,但在功能不太强大的Linux PC上运行时会崩溃或严重滞后。我最小化了代码,使其仅为一个小类,但也存在完全相同的滞后性。我认为这与缓冲区策略和线程有关。下面是课程: public class Test extends Canvas implements Runnable { private Thread thread; private boolean running = false; public Test()

我有一个小引擎,在我的OSX笔记本电脑上运行得很好,但在功能不太强大的Linux PC上运行时会崩溃或严重滞后。我最小化了代码,使其仅为一个小类,但也存在完全相同的滞后性。我认为这与缓冲区策略和线程有关。下面是课程:

public class Test extends Canvas implements Runnable {

    private Thread thread;
    private boolean running = false;

    public Test()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(new Dimension(1000, 1000));
        frame.add(this);
        frame.setVisible(true);
        start();
    }

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

    public synchronized void start()
    {
        this.thread = new Thread(this);
        this.thread.start();
        this.running = true;
    }

    public synchronized void stop()
    {
        try
        {
           this.thread.join();
           this.running = false;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    public void run()
    {
        while(running)
        {
            render();
        }
    }

    private void render()
    {
        BufferStrategy bs = this.getBufferStrategy();
        if(bs == null)
        {
            this.createBufferStrategy(2);
            return;
        }

        Graphics g = bs.getDrawGraphics();
        g.setColor(Color.black);
        g.fillRect(0, 0, 1000, 1000);
        g.dispose();
        bs.show();
    }
}

我看到画布被漆成黑色之前的延迟。在将JFrame设置为可见之前,必须在画布上绘制一些内容

以下是我对您的代码所做的更改

  • 我用的是JPanel而不是画布。我用JPanel实现了自动双缓冲

  • 我通过调用SwingUtilities invokeLater方法启动了Swing应用程序。这会将Swing组件的创建和执行放在服务器上。Oracle和我坚持所有Swing应用程序都以这种方式启动。在后面的解释中,您将看到为什么这是至关重要的

  • 我每250毫秒画一幅白色图像,然后再画一幅黑色图像。这样,我确信绘画代码是有效的

  • 我将线程代码移到了它自己的类中。我遇到了一个问题,线程和JPanel使用布尔值,通过将线程放在自己的类中,我可以为线程和JPanel创建单独的布尔值

  • 在JPanel paintComponent方法中,我在设置JFrame可见之前绘制了一些东西

  • 我设置了JPanel的首选大小,而不是JFrame的大小。无论如何,我想知道绘图面板的大小

  • 在线程代码中,对图形JPanel的方法调用放在SwingUtilities invokeLater方法中。这确保了绘制在事件分派线程上进行,而计时循环在不同的线程中执行。这确保GUI保持响应性

  • 这是密码

    package com.ggl.testing;
    
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class DrawingTest extends JPanel {
        private static final long serialVersionUID = 2584117430541789858L;
    
        private DrawingTestRunnable drawingTestRunnable;
    
        private boolean isWhite;
    
        public DrawingTest() {
            this.setPreferredSize(new Dimension(1200, 700));
            this.isWhite = true;
    
            JFrame frame = new JFrame("Drawing Test");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(this);
            frame.pack();
            frame.setVisible(true);
            start();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new DrawingTest();
                }
            });
        }
    
        public synchronized void start() {
            drawingTestRunnable = new DrawingTestRunnable(this);
            new Thread(drawingTestRunnable).start();
        }
    
        public boolean isWhite() {
            return isWhite;
        }
    
        public void setWhite(boolean isWhite) {
            this.isWhite = isWhite;
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
    
            if (isWhite) {
                g.setColor(Color.WHITE);
            } else {
                g.setColor(Color.BLACK);
            }
    
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            g.dispose();
        }
    
        public class DrawingTestRunnable implements Runnable {
            private boolean isWhite;
            private volatile boolean running;
    
            private DrawingTest drawingTest;
    
            public DrawingTestRunnable(DrawingTest drawingTest) {
                this.drawingTest = drawingTest;
                this.running = true;
            }
    
            @Override
            public void run() {
                long duration = 250L;
                long startTime = System.currentTimeMillis();
                while (running) {
                    repaintPanel();
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    long loopDuration = Math.max((duration - elapsedTime), 5L);
                    sleep(loopDuration);
                    startTime = System.currentTimeMillis();
                }
            }
    
            private void repaintPanel() {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        drawingTest.repaint();
                        isWhite = !isWhite;
                        drawingTest.setWhite(isWhite);
                    }
                });
            }
    
            private void sleep(long duration) {
                try {
                    Thread.sleep(duration);
                } catch (InterruptedException e) {
    
                }
            }
    
            public synchronized void setRunning(boolean running) {
                this.running = running;
            }
    
        }
    }
    

    我看到画布被漆成黑色之前的延迟。在将JFrame设置为可见之前,必须在画布上绘制一些内容

    以下是我对您的代码所做的更改

  • 我用的是JPanel而不是画布。我用JPanel实现了自动双缓冲

  • 我通过调用SwingUtilities invokeLater方法启动了Swing应用程序。这会将Swing组件的创建和执行放在服务器上。Oracle和我坚持所有Swing应用程序都以这种方式启动。在后面的解释中,您将看到为什么这是至关重要的

  • 我每250毫秒画一幅白色图像,然后再画一幅黑色图像。这样,我确信绘画代码是有效的

  • 我将线程代码移到了它自己的类中。我遇到了一个问题,线程和JPanel使用布尔值,通过将线程放在自己的类中,我可以为线程和JPanel创建单独的布尔值

  • 在JPanel paintComponent方法中,我在设置JFrame可见之前绘制了一些东西

  • 我设置了JPanel的首选大小,而不是JFrame的大小。无论如何,我想知道绘图面板的大小

  • 在线程代码中,对图形JPanel的方法调用放在SwingUtilities invokeLater方法中。这确保了绘制在事件分派线程上进行,而计时循环在不同的线程中执行。这确保GUI保持响应性

  • 这是密码

    package com.ggl.testing;
    
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class DrawingTest extends JPanel {
        private static final long serialVersionUID = 2584117430541789858L;
    
        private DrawingTestRunnable drawingTestRunnable;
    
        private boolean isWhite;
    
        public DrawingTest() {
            this.setPreferredSize(new Dimension(1200, 700));
            this.isWhite = true;
    
            JFrame frame = new JFrame("Drawing Test");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(this);
            frame.pack();
            frame.setVisible(true);
            start();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new DrawingTest();
                }
            });
        }
    
        public synchronized void start() {
            drawingTestRunnable = new DrawingTestRunnable(this);
            new Thread(drawingTestRunnable).start();
        }
    
        public boolean isWhite() {
            return isWhite;
        }
    
        public void setWhite(boolean isWhite) {
            this.isWhite = isWhite;
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
    
            if (isWhite) {
                g.setColor(Color.WHITE);
            } else {
                g.setColor(Color.BLACK);
            }
    
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            g.dispose();
        }
    
        public class DrawingTestRunnable implements Runnable {
            private boolean isWhite;
            private volatile boolean running;
    
            private DrawingTest drawingTest;
    
            public DrawingTestRunnable(DrawingTest drawingTest) {
                this.drawingTest = drawingTest;
                this.running = true;
            }
    
            @Override
            public void run() {
                long duration = 250L;
                long startTime = System.currentTimeMillis();
                while (running) {
                    repaintPanel();
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    long loopDuration = Math.max((duration - elapsedTime), 5L);
                    sleep(loopDuration);
                    startTime = System.currentTimeMillis();
                }
            }
    
            private void repaintPanel() {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        drawingTest.repaint();
                        isWhite = !isWhite;
                        drawingTest.setWhite(isWhite);
                    }
                });
            }
    
            private void sleep(long duration) {
                try {
                    Thread.sleep(duration);
                } catch (InterruptedException e) {
    
                }
            }
    
            public synchronized void setRunning(boolean running) {
                this.running = running;
            }
    
        }
    }
    

    对于阅读本文的其他人来说,混合使用Swing轻量级和AWT重量级组件不是一个好主意。如果您想在Swing应用程序中绘图,请使用JPanel。如果创建2D游戏,是否建议使用AWT over Swing?否。有关使用Swing的图形应用程序示例,请参阅我的答案。对于阅读本文的其他人,混合使用Swing轻量级组件和AWT重量级组件不是一个好主意。如果您想在Swing应用程序中绘图,请使用JPanel。如果创建2D游戏,是否建议使用AWT over Swing?否。有关使用Swing的图形应用程序示例,请参阅我的答案。