Using Swing Timers for Animation
A Swing Timer's thread only generates events; the handler code that responds to them runs on the Swing event-handling thread, so using a timer is not the same as running animation on a separate thread.
Why a Timer Is Not a Background Thread
A common first plan for animating a Swing application goes like this: if you can write an animation loop that runs on its own thread, then a Swing Timer must do the same job with less code. The two approaches look similar on the surface, and that similarity is exactly what makes the plan tempting. They are not the same thing. The two approaches place your animation logic in different threads, and that difference determines whether your program is safe.
Animation on a screen is an illusion built from a simple ingredient: a series of still pictures shown one after another. Java needs some way to decide when each new picture should appear, and the tool it offers for that job is a Timer, an object that produces a steady beat of events. Each beat gives your program an opportunity to compute the next picture and draw it. This article starts from a Timer-driven animation and works backward to the mechanism that makes it move, so that when something goes wrong you know where to look.
A Swing Timer's own thread does one job: it generates events at regular intervals. It does not draw anything and it does not update your animation state. The code that responds to each event runs on the Swing event-handling thread.
Which Thread Runs the Handler
Think of the timer as a source of ticks. Its underlying thread generates an event at each interval, and then the event is handled by code that executes on the Swing event-handling thread. That same thread is responsible for repainting components and for responding to user actions such as mouse clicks and key presses. So the timer's thread and the handler's thread are different threads, and the handoff between them is what the diagram below traces.
The practical consequence is that the Swing event-handling thread is safe only when all the GUI work stays on it. As long as everything involving components and their variables happens there, the system behaves predictably. Trouble starts when another thread reaches into the same components or the same variables. Using a Timer keeps your handler on the right thread; writing your own loop on a separate thread does not.
Where a Race Condition Comes From
Consider an animation class in which one method, updateForNextFrame(), changes several variables that describe the current frame, such as a position and a size. Another method, paintComponent(), reads those same variables in order to draw the frame. Suppose the animation logic calls updateForNextFrame() from a thread other than the Swing event-handling thread while paintComponent() is running on the Swing event-handling thread. The two methods can then overlap in time.
Here is the interleaving that causes trouble. The background thread begins updateForNextFrame() and assigns the new position. Before it gets to the new size, the Swing thread starts paintComponent() and reads the position and the size. The painter now sees a new position paired with an old size. The background thread then finishes its assignments, but the picture has already been drawn from a mixture of values. Each individual assignment was correct on its own; the problem is that the reader saw the variables in the middle of a change.
Why repaint Is Safe From Any Thread
Not every GUI call carries the same risk. The repaint() method of a component can be called safely from any thread, without worrying about synchronization. Beginners often worry about this call unnecessarily. The reason is that repaint() does not paint anything itself. It only asks the system to schedule a paint event, and the actual painting is carried out later on the Swing event-handling thread.
So an animation thread can request a repaint freely, but it should not perform the drawing or change the variables that the drawing depends on without coordination. Requesting a repaint is a message to the painting machinery; changing the data that the paint reads is the part that still needs care.
What do you think happens?
A background thread calls repaint() on a component. Does that call need synchronization to be safe?
Reveal answer
No, repaint() only schedules a paint event
repaint() does not paint anything itself. The painting happens later on the Swing event-handling thread. The synchronization concern is about the variables the painting reads, not about the repaint() call.
What a Swing Timer Actually Does
A javax.swing.Timer exists to generate ActionEvent events at a fixed delay. It can also be configured to fire a single event after a delay, in which case it acts like an alarm rather than a metronome. For animation the repeating form is the one that matters, because a moving picture needs a new frame at every tick.
A Timer does not do anything else with its events. It does not draw, compute, or update any data. Its only job is to announce that a moment has arrived. Whatever should happen at that moment has to be written somewhere else, in an object set up to listen for those announcements.
The source also notes that Java includes a different class, java.util.Timer, which suits non-GUI programs. The choice depends on whether the work touches Swing components at all. A Swing Timer is designed to work with the Swing event-handling thread, which is what makes it a natural fit for GUI animation.
Wiring a Listener to the Timer
A Timer on its own is not useful, because there is no point in a timer without something to respond to its events. The listener is therefore supplied when the Timer is constructed, and the delay between events is supplied at the same time. The listener must be an object that implements the ActionListener interface. In practice its class is declared to implement ActionListener and defines the one method the interface requires, actionPerformed. The method runs each time the Timer generates an event.
- BallMover implements
ActionListener, so it can be the listener actionPerformedcontains the work done on each tick- new
Timer(40, mover)supplies the delay and the listener at construction timer.start()is the call that begins the event stream
Starting, Stopping, and Restarting
Creating a Timer and starting it are two separate steps. A timer does not begin generating events when the object is created. Until start() is called the Timer exists but is quiet, and the listener's actionPerformed method is never invoked. The stop() method turns the stream of events off, and the timer can be restarted later by calling start() again. A running Timer therefore moves through a small cycle: created but idle, running, stopped, and running again.
From Still Frames to Perceived Motion
A computer animation is a sequence of still images presented one after another. Two conditions decide whether the viewer perceives continuous motion: the time between images must be short, and the change from one image to the next must not be too great. If both hold, the eye reads the sequence as one moving object rather than a slideshow. The Timer supplies the rhythm, and the code in actionPerformed supplies the content of each frame.
Tracing one ball across three ticks
A timer fires every 40 milliseconds. Its listener adds 5 to x on each tick and then requests a repaint. The ball starts at x = 0. What does the viewer see across the first three ticks?
- 1Tick 1
The Timer generates an event and
actionPerformedruns on the Swing thread. x becomes 5,repaint()is requested, and the frame is painted with the ball at 5. - 2Tick 2
After the delay, the next event arrives. x becomes 10,
repaint()is requested, and the next frame is painted with the ball at 10. - 3Tick 3
The third event arrives. x becomes 15, and the frame is painted with the ball at 15.
- 4Perception
Three separate still frames were drawn. Because each step is small and frames arrive quickly, the viewer perceives the ball sliding right.
Three still frames at x = 5, 10, and 15 are shown in sequence and read as continuous rightward motion.
Finding Where Behavior Diverges
When an animation does not behave as expected, split the system into three checkpoints and work through them in order: first, whether the Timer was started; second, whether events are arriving at the listener; and third, whether actionPerformed computes and draws the frame you intended. The first checkpoint that fails tells you where the divergence happens.
Mistakes Beginners Make with Timers
Common mistake Assuming a Timer is simply a background thread that can update the animation state
Why it failsThe timer's thread only generates events. The handler runs on the Swing event-handling thread, so the state changes belong there.
Do this insteadPut the state update inside actionPerformed and let the Swing thread run it.
Common mistake Writing a separate animation loop on its own thread and letting it change the variables that paint reads
Why it failsThe painter can read variables in the middle of an update, producing a race condition and a picture built from mixed frame data.
Do this insteadUse a Timer so the update and the painting share the Swing thread, or synchronize access to the shared variables.
Common mistake Expecting a Timer to run as soon as it is created
Why it failsA Timer does not start on creation. Until
start()is called,actionPerformedis never invoked.Do this insteadCall start() after constructing the Timer.
Common mistake Wrapping every repaint() call in synchronization
Why it failsrepaint()only schedules a paint event, so it is safe to call from any thread.Do this insteadCall repaint() freely, and reserve synchronization for variables that painting reads.
Tracing a Stopped Animation
A program creates a Timer with a listener that moves a ball, but the ball never moves. The code constructs the Timer and never calls start() on it. Using the three checkpoints, identify which checkpoint fails first and explain what you would change.
Hints
- Start with the first checkpoint: was the Timer started?
- Consider what the Timer does between construction and the
start()call. - The fix involves a single method call on the Timer object.
Key Takeaways
- A Swing Timer's thread only generates events; the handler in
actionPerformedruns on the Swing event-handling thread, so using a Timer is not the same as running animation on a separate thread. - A race condition arises when a non-GUI thread changes variables that painting reads, because the painter can see a mixture of old and new values.
- A Timer generates ActionEvents at a fixed delay, delivered to an
ActionListener'sactionPerformed; it must be started withstart()and turned off withstop(). repaint()is safe from any thread because it only schedules a paint event that is carried out later on the Swing event-handling thread.- Motion comes from a sequence of still frames, each computed on a tick, drawn after
repaint(), and shown quickly enough to be perceived as continuous.