views:

130

answers:

6

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 the 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?

+1  A: 

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.

Joel
Which variables? The fields are declared in a system he has no access to, and making local variables volatile is senseless, since only one thread will access them.
meriton
I don't think that there is any issue with the values of those variables, as they're only being accessed within a single thread
gab
@meriton I changed my answer while you posted your comment..
Joel
Exactly meriton. And I also have to have the result from both methods at what is essentially the same point in time (it doesn't matter precisely what point in time, just that it's the same for both variables). `totalFoo`s can't change before I access `barredFoo`s, otherwise the subtraction to find `nonBarredFoo`s will be thrown off.
Lawrence Johnston
hmm, yeah, should have read the question more carefully - missed the bit about network calls. mea culpa.
Joel
+3  A: 

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.

gab
+1. In order to not have to play the retry-hokey-pokey, you would need to synchronize your method with the methods from the system you're interacting with, which is of course, impossible in your situation.
Chris
His implementation isn't sufficient as the VM is allowed to reorder the calls such that the call to getBarredFoos() could appear to come after *both* calls to getTotalFoos(). See Brian Goetz's "Java Concurrancy in Practice" for details.
Steve Emmerson
Thanks for the input. In the actual implementation I am taking the possible infinite loop into account.
Lawrence Johnston
@Steve Emmerson Thank you, that was exactly the kind of thing I was concerned about. Is there anyway to insure that they do happen in the proper sequence?
Lawrence Johnston
Will wrapping either the two network calls in synchronized methods or the method which calls the two methods really guarantee that the methods are called in order? As far as I'm aware they wont.
Lawrence Johnston
Synchronize the code block http://www.ibm.com/developerworks/library/j-jtp02244.html
Joel
@Lawrence - I think synchronisation does guarantee execution order - see Brians article in the link above on fixing the JMM
Joel
@Steve Emmerson: true that the JVM can re-order operations, but only if that re-ordering does not influence the function of a single thread (ie without taking other threads into account). If I'm not mistaken, re-ordering those operations would greatly affect the logic in this thread, so that reordering could not happen.
gab
I agree with gab. Imagine if I/O Operations could indeed be freely reordered by the virtual machine. That would imply that reading from a file written by a previous method call would be unsafe, as the vm could reorder the read before the write. That would be a totally useless programming model. Granted, the Java memory model has its surprising aspects, but they only occur when several threads coexist within a single virtual machine, which is not the case here.
meriton
The only way I can see his double-checking working is *if* the get...() methods invoke the same synchronization method that is used to maintain the invariant while the values are modified (which they're not documented to do).
Steve Emmerson
I also agree with gab. @Steve He says that both counters are incremented and synched, so if getTotalFoos() hasn't changed, then neither has getBarredFoos(). He doesn't specifically say the gets are synchronized with the updates, but if they weren't, the statement that 'any given point in time the two counters are in synch' would be false.
Robin
+1 gab, as long as the counters are (a) monotonically increasing, and (b) kept properly in sync at the server end, and (c) kept properly PUBLISHED at the server end (not the same thing!) then this is sufficient. Reordering is irrelevant here as the JVM is only permitted to reorder when the apparent order of operations is not affected. i.e. this can no more be reordered problematically than 'a = 3; a = 2;' can be reordered to show 'a == 3' at the end, which is to say 'not at all'.
Cowan
A: 

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).

Romain
Is that so? Can you give an example when his code would fail?
meriton
@meriton - if barredFoos had changed after checking that totalFoos had not changed
Joel
@Joel: why would that matter? The code does not retrieve the current barredFoos after checking that totalFoos has not changed, but uses the values retrieved earlier, which must be consistent, since totalFoos did not change.
meriton
@meriton - you are assuming that totalFoos only changes if barredFoos changes (and vica versa) whereas I had assumed that this was not necessarily the case.
Joel
This definitely makes sense. Though the example may be overly simplistic and certainly doesn't scale to a larger number of counters, does it?
Romain
@Joel: All that matters is that the snapshot is consistent. It doesn't need to be the "latest" at any given point in time.
Romain
A: 

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[].

Chad Okere
Unfortunately, this is a situation where the system maintaining the counters is not controlled by my company.
Lawrence Johnston
A: 

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.

Steve Emmerson
Thinking further, can you guarantee that both the get...() methods use the same synchronization mechanism during value retrieval that is used when updating either value? If so, then I think your hack will work because the get...()'s will enforce ordering.
Steve Emmerson
Interesting point. I'll look into it.
Lawrence Johnston
A: 

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()
ChadNC
Why? What does synchronizing single-threaded code accomplish?
meriton
@Meriton. Thnaks for pointing the mistake out. Staring at the ACORD schema for the last week has made my eyes hazy
ChadNC
Please explain the reason for the downvote so that I can understand my errors. Downvoting without an explanation is ridiculous and should be considered spam.
ChadNC
It wasn't me, but: You don't state why you think synchronization is needed. I happen to think it isn't, because there is only one thread in this virtual machine.
meriton