Why does the Java Swing timer lead to less animation than sleep?

I have two almost identical classes: AnimationFrame1 and AnimationFrame2. Both of these classes display a blue ball moving back and forth horizontally through a 500 x 500 window. The two classes are identical, except for the runAnimation () and createAndShowGUI () methods. In its runAnimation () method, AnimationFrame1 uses a while and sleep method to create an animation loop, while AnimationFrame2 uses a Swing timer. In its createAndShowGUI () method, AnimationFrame1 creates a new thread and calls the runAnimation () method on it, while AnimationFrame2 just calls the runAnimation () method without a new thread.

After compiling both classes, I found that AnimationFrame2, the one that uses the Swing timer, displays a much smoother animation that doesn't stutter as much as the animation displayed in AnimationFrame1, which uses the while and sleep loop. My question is: why is AnimationFrame1 more stuttering in animation than AnimationFrame2? I searched for this reason, but still haven't found anything.

Also, I am obviously a beginner of Java, so please let me know if you see something wrong with my code or know how I could improve it.

Here is the AnimationFrame1:

import javax.swing.*; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; class AnimationFrame1 extends JPanel { int ovalX; int prevX; Timer timer; boolean moveRight; BufferedImage img; public AnimationFrame1() { setPreferredSize(new Dimension(500, 500)); } public void runAnimation() { moveRight = true; img = null; ovalX = 0; prevX = 0; while(true) { if (moveRight == true) { prevX = ovalX; ovalX = ovalX + 4; } else { prevX = ovalX - 4; ovalX = ovalX - 4; } repaint(); if (ovalX > 430) { moveRight = false; } if (ovalX == 0) { moveRight = true; } try { Thread.sleep(25); } catch(Exception e) { } } } public void paintComponent(Graphics g) { if (img == null) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = getGraphicsConfiguration(); img = gc.createCompatibleImage(78, 70); Graphics gImg = img.getGraphics(); gImg.setColor(getBackground()); gImg.fillRect(0, 0, getWidth(), getHeight()); gImg.setColor(Color.BLUE); gImg.fillOval(4, 0, 70, 70); gImg.dispose(); } g.drawImage(img, ovalX, 250, null); } public static void createAndShowGUI() { JFrame mainFrame = new JFrame(); final AnimationFrame1 animFrame = new AnimationFrame1(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainFrame.add(animFrame); mainFrame.pack(); mainFrame.createBufferStrategy(2); mainFrame.setVisible(true); new Thread(new Runnable() { public void run() { animFrame.runAnimation(); } }).start(); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } } 

And here is AnimationFrame2:

 import javax.swing.*; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; class AnimationFrame2 extends JPanel { int ovalX; int prevX; Timer timer; boolean moveRight; BufferedImage img; public AnimationFrame2() { setPreferredSize(new Dimension(500, 500)); } public void runAnimation() { moveRight = true; img = null; ovalX = 0; prevX = 0; timer = new Timer(25, new ActionListener() { public void actionPerformed(ActionEvent ae) { if (moveRight == true) { prevX = ovalX; ovalX = ovalX + 4; } else { prevX = ovalX - 4; ovalX = ovalX - 4; } repaint(); if (ovalX > 430) { moveRight = false; } if (ovalX == 0) { moveRight = true; } } }); timer.start(); } public void paintComponent(Graphics g) { if (img == null) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = getGraphicsConfiguration(); img = gc.createCompatibleImage(78, 70); Graphics gImg = img.getGraphics(); gImg.setColor(getBackground()); gImg.fillRect(0, 0, getWidth(), getHeight()); gImg.setColor(Color.BLUE); gImg.fillOval(4, 0, 70, 70); gImg.dispose(); } g.drawImage(img, ovalX, 250, null); } public static void createAndShowGUI() { JFrame mainFrame = new JFrame(); final AnimationFrame2 animFrame = new AnimationFrame2(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainFrame.add(animFrame); mainFrame.pack(); mainFrame.createBufferStrategy(2); mainFrame.setVisible(true); animFrame.runAnimation(); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } } 
+4
source share
2 answers

After putting the markers in the code, it seems that the Timer version actually starts every 30 ms, while the Thread.sleep version starts every 25 ms. There may be several explanations, including:

  • timer resolution, which is not as good as Thread.sleep
  • the fact that the timers are single-threaded (except for waiting, everything runs in the EDT), therefore, if a task (for example, repainting) takes more than 25 ms, it delays the next task.

If I increase sleep to 30 ms, 2 animations are similar (the actual number may vary depending on your device).

Note. Thread.sleep has a thread safety issue. You share variables between a workflow and a user interface thread without proper synchronization. Although it seems that repaint internally introduces a synchronization barrier that allows visibility of changes made by the workflow from the user interface thread, this is a side effect, and it would be better to use explicit visibility, for example, by declaring variables unstable.

+8
source

The cause of the problem is most likely due to a "violation" of AWT semantics in the first version. you cannot run gui update code outside of EDT.

UPDATE: even if the repaint() method is safe to call from another thread, all that it does is the sequence of events that will run on EDT. this means that there is a race condition between the thread that changes the oval and thread of the EDT thread. this will cause the movement to be uneven, as the drawing code can see different values ​​than the alarm code suggests.

+2
source

All Articles