Win a copy of Functional Reactive Programming this week in the Other Languages forum!
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

Broken Swing Timer in Java 7

 
Luigi Plinge
Ranch Hand
Posts: 441
IntelliJ IDE Scala Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The accuracy of Swing's Timer has gone out of the window. Here's a demo:
Tested on Window 7 64-bit.

Java 6 r22 :
Enter delay in ms: 50
Expecting 20.0 events per second
20 events in 1.01 s
20 events in 1.0 s
20 events in 1.0 s

Enter delay in ms: 20
Expecting 50.0 events per second
50 events in 1.0 s
50 events in 1.0 s
50 events in 1.0 s

Enter delay in ms: 10
Expecting 100.0 events per second
99 events in 1.0 s
100 events in 1.0 s
100 events in 1.0 s

Java 7:
Enter delay in ms: 50
Expecting 20.0 events per second
16 events in 1.01 s
17 events in 1.01 s
17 events in 1.02 s

Enter delay in ms: 20
Expecting 50.0 events per second
33 events in 1.02 s
34 events in 1.01 s
34 events in 1.02 s

Enter delay in ms: 10
Expecting 100.0 events per second
48 events in 1.0 s
50 events in 1.0 s
50 events in 1.0 s


Let's just hope no-one's tempted to use Java 7 in any production systems...

[Edit: changed subject to be less confrontational]
 
Darryl Burke
Bartender
Posts: 5148
11
Java Netbeans IDE Opera
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Were both tests run on the same computer? or at least the same OS and identical hardware?
 
Rob Spoor
Sheriff
Pie
Posts: 20669
65
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I wouldn't blame just Java 7. I just tried your code with Java 6u26 on Windows 7 64 bit, and with twice a 50ms delay I got the following results:

Enter delay in ms: 50
Expecting 20.0 events per second
11 events in 1.0 s
17 events in 1.06 s
17 events in 1.06 s
17 events in 1.06 s
17 events in 1.038 s
17 events in 1.06 s


Enter delay in ms: 50
Expecting 20.0 events per second
20 events in 1.002 s
20 events in 1.0 s
20 events in 1.0 s
20 events in 1.0 s
20 events in 1.0 s
20 events in 1.0 s


As you can see there's a 3 event difference with the same hardware, same software, same program. There are a lot of external influences (e.g. other processes running) that can influence the results.
 
Luigi Plinge
Ranch Hand
Posts: 441
IntelliJ IDE Scala Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It could possibly be something introduced in a late edition of Java 6, since I only have u22. But it is a repeatable bug, which is independent on external influences. If I switch back and forth between the Java 6_22 and 7 libraries, the wrong timing only happens with 7. (It doesn't make a difference which version is used to compile it.)
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Timer runs a thread which might see a cached value, this is a concurrency issue. use volitile keyword on primatives especally long(64-bit can be made of2 32-bit segments), and/or synchronised blocks around compound action0s (delay, count++).
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I typed on a misaligned touch screen the other day, and I wasn't able to be more descriptive then, so here I go again:

I read in, Goetz. Java Concurrency in Practice, that 64-bit value types, double and long, can be represented by two 32-bit values on some systems. It could be the case that only half of your long is being updated, I can imagine that this can especially be a problem on multi-core cpu systems. Therefore, you shouldn't assume that just because you have a 64-bit system that your JavaVM is optimized for it.

To add to the problem, a new Thread creates a new stack of its own and can, if it tries to access a value outside of itself, such as your call to time1 = System.currentTimeMillis(), see a stale, old value, or in the case of split 64-bit data types half of an old value and half an update. This throws the time worthiness out the window.

Also "count++" is actually two compound actions, first read the value then update it, that can result in a "missing update" bug that sets your count backwards, the Thread may fall asleep after it has read count but before completing the increment.

I think you should try to get some concurrency safeguards in place, such as atomic variables, or volatile on long, or synchronized blocks around compound actions in your thread, and try again.
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Okay, you might want to skip the thread counts all together. Just check out Java VisualVM, if you're using Windows look in your Java folder/bin/jvisualvm.exe. It actually keeps track of how many threads are running in each Java program you're running.
 
Luigi Plinge
Ranch Hand
Posts: 441
IntelliJ IDE Scala Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hey Walter, all I know is that it used to work just fine, they changed something, and now it doesn't! A quick comparison of the source codes reveals quite a few changes in the Timer class. I don't think the fault is with my little test example. It's a very simple and typical use-case.
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Curious, I looked at the src code of Java 6 and 7.

The first thing I notice is that they've replaced a boolean with an AtomicBoolean:

6

7


Since this AtomicBoolean reference is marked final (I assume this means it will always point to the same object), and, I noticed, AtomicBoolean src uses an immutable storage field, I conclude the notify field in Timer will always be false -- the class can't reassign a true AtomicBoolean to that reference field. It will affect the event/listener model, but I don't know to what effect this is having on performance issue you brought up.

I'm continuing to look through the source code...
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Okay, I don't like where this is going. Private fields probably don't need this level of thread safety when they are only used for an Object's internal computations. And, some of these fields are changing from mutable into immutable references, when they weren't originally. This will be trouble if they didn't alter the programming logic.

 
Rob Spoor
Sheriff
Pie
Posts: 20669
65
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Walter Gabrielsen Iii wrote:Since this AtomicBoolean reference is marked final (I assume this means it will always point to the same object), and, I noticed, AtomicBoolean src uses an immutable storage field, I conclude the notify field in Timer will always be false -- the class can't reassign a true AtomicBoolean to that reference field.

AtomicBoolean has methods to change its value.

As for these changed fields, I'd say that making them volatile or final is an improvement for thread safety. Both ensure that a thread will always use the most up-to-date value for the field, instead of a possibly old cached value.

Walter Gabrielsen Iii wrote:Private fields probably don't need this level of thread safety when they are only used for an Object's internal computations.

The private fields are just as much part of the internal state of an object as non-private fields. To ensure this internal state is valid synchronization techniques are often required, also for private fields. Using volatile and final are two such techniques.
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
But, aren't the Timer's internal calculations being run on the same stack/heap area as the Timer object? That is to say there aren't threads running inside Timer to set its state, the fields are encapsulated, and the only access to them is from method calls.
 
Rob Spoor
Sheriff
Pie
Posts: 20669
65
Chrome Eclipse IDE Java Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
And can you tell with 100% certainty that there is only one thread calling these methods? Hint: package default class javax.swing.TimerQueue which is used by Timer implements Runnable. And it has a method startIfNeeded with the following code:
This method is called from the TimerQueue constructor which is called from static method TimerQueue.sharedInstance() which is called from Timer's timerQueue method which is used all over the place.

So I'll answer my own question: no, there's probably not one thread calling these methods. There are at least two: the TimerQueue thread and the Event Dispatcher Thread.
 
Darryl Burke
Bartender
Posts: 5148
11
Java Netbeans IDE Opera
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
That's also documented in the API for javax.swing.Timer.
Although all Timers perform their waiting using a single, shared thread (created by the first Timer object that executes), the action event handlers for Timers execute on another thread -- the event-dispatching thread.

 
Rob Spoor
Sheriff
Pie
Posts: 20669
65
Chrome Eclipse IDE Java Windows
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Pffff, who reads the API if you can go through code?
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I followed Timer's "this" reference into TimerQueue. The developers also heavily remade that class using the java.util.concurrent.* packages. I can see why thread safety is important.

I just wonder if they over-did the protections. If that is having a performance hit (having to lock and rebuild Timer's "this" reference, which doesn't really leave the TimerQueue until after its repackaged as a subclass thread, finished its delay time, and then fires its ActionEvent). It seems like a lot of trouble to go through just to fire an ActionEvent (I think it just jumped right to start(), using synchronized methods , in Java 6). Did they just go crazy about removing as much non-concurrent package thread-safety-techniques as they could?
 
Walter Gabrielsen Iii
Ranch Hand
Posts: 158
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have an idea. What if the new thread safety of Timer is there because they removed the synchronized methods of TimerQueue? Wouldn't that also mean the new Event method now needs to be synchronized as well?
 
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic