Basic Java threading issue
Lets say I'm interacting with a system that has two incrementing counters which depend on each other (these counters will never decrement): int totalFoos; // barredFoos plus nonBarredFoos int barredFoos;
I also have two methods: int getTotalFoos(); // Basically a network call to localhost int getBarredFoos(); // Basically a network call to localhost
These two counters are kept and incremented by code that I don't have access to. Let's assume that it increments both counters on an alternate thread but in a thread-safe manner (i.e. at any given point in time the two counters will be in sync).
What is the best way to get an accurate count of both barredFoos and nonBarredFoos at a single point in time?
The completely naive implementation:
int totalFoos = getTotalFoos();
int barredFoos = getBarredFoos();
int nonBarredFoos = totalFoos - barredFoos;
This has the issue that the system could increment both counters in between the two method calls and then my two copies would be out of sync and barredFoos
would have a value of more than it did when totalFoos
was fetched.
Basic double-checked implementation:
while (true) {
int totalFoos = getTotalFoos();
int barredFoos = getBarredFoos();
if (totalFoos == getTotalFoos()) {
// totalFoos did not change during fetch of barredFoos, so barredFoos should be accurate.
int nonBarredFoos = totalFoos - barredFoos;
break;
}
// totalFoos changed during fetch of barredFoos, try again
}
This should work in theory, but I'm not sure that the JVM guarantees that this is what actually happens in practice once optimization and such is taken into account. For an example of these concerns, see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html (Link via Romain Muller).
Given the methods I have and t开发者_如何转开发he assumption above that the counters are in fact updated together, is there a way I can guarantee that my copies of the two counts are in sync?
Yes, I believe your implementation will be sufficient; the real work is making sure that the values that are returned by getTotalFoos
and getBarredFoos
are indeed synchronized and always returning the latest values. However, as you've said, this is already the case.
Of course, one thing you could run in to with this code is an endless loop; you would want to be sure that the two values being changed in such a short time would be a very exceptional situation, and even then I think that it would definitely be wise to build in a safety (ie maximum number of iterations) to avoid getting into an endless loop. If the value coming out of those counter is in code that you don't have access to, you don't want to be totally relying on the fact that things will never go awry at the other end.
To guarantee read consitency across threads - and prevent code execution re-ordering, especially on muli-core machines, you need to synchronize all read and write access to those variables. In addition, to ensure that on a single thread you see the most up to date values of all variables being used in the current computation you need to synchronise on read access.
Update: I missed the bit about the calls to get the values of both variables being separate calls over the network - which renders this the double-checked locking problem (so without an api method available to you that returns both values at once you cann't absolutely guarantee consistency of both variables at any point in time).
See Brian Goetz's article on Java memory model.
You can probably not reliably do what you want unless the system you are interacting with has a method that enables you to retrieve both values at once (in an atomic way).
I was going to mention AtomicInteger as well, but that won't work, because
1) you've got TWO integers, not just one. AtomicIntegers won't help you. 2) He doesn't have access to the underlying code.
My question is, even if you can't modify the underlying code, can you control when it's executed? You could put synchronization blocks around any functions that modify those counters. That might not be pleasant, (it could be slower then your loop) but that would arguably be the 'correct' way to do it.
If you can't even control the internal threads, then I guess your loop would work.
And finally, if you ever do get control of the code, the best thing would be to have one synchronized function that blocks access to both integers as it runs, and returns the two of them in an int[].
Given that there's no way to access whatever locking mechanism is maintaining the invariant "totalFoos = barredFoos + nonBarredFoos", there's no way to ensure that the values you retrieve are consistent with that invariant. Sorry.
The method that contains the code
while (true) {
int totalFoos = getTotalFoos();
int barredFoos = getBarredFoos();
if (totalFoos == getTotalFoos()) {
int nonBarredFoos = totalFoos - barredFoos;
break;
}
}
Should be synchronized
private synchronized void getFoos()
精彩评论