2

Consider a statistics class in Java counting successes and failures.

public class Stat {
  long successes=0, failures=0;
  public success() {successes += 1;}
  public failed() {failures += 1;}
  public logStats() { ... read the values and log them ... }
}

The logStats() method shall be called from another thread regularly to log the current statistics counters. This code is wrong then, because the logger thread may not see the most recent values, since nothing is synchronized or volatile or atomic.

Because I am using long not even volatile would suffice. And even this makes the increment more expensive than without. Assuming that counting is done in really high frequency, while the logs run only once a minute, is there a way to force the fresh values to be distributed to all threads when entering logStats()? Would it work to make only logStats() synchronized. Kind of a half-sided synchronization. I know the books say don't do it. I am just trying to understand whether in this specific setting it would work and why.

In addition I should note that only one thread ever does the counting.

EDIT Please read carefully what the question is. I am not asking how to implement this differently. I am asking whether and why there is some half-sided consistency enforcement whereby the write thread does not care but the reader thread actively forces to see the most recent values. My hunch is that it may work with only one synchronize but I cannot explain yet why or why not.

Community
  • 1
  • 1
Harald
  • 4,575
  • 5
  • 33
  • 72

4 Answers4

2

Java provides no way to implement your "half-sided" concurrency, and I doubt even the hardware does.

However, you may want to question how important the synchronization guarantees are to you. It is true that the language doesn't guarantee it, but as long as you're running on a 64-bit platform, long accesses are certainly going to be "atomic" in the sense that your program is not going to do them in two 32-bit read cycles, even if neither volatile nor synchronized. It's not like you're synchronizing on anything else, so I doubt that it is really important to you that your logger gets the truly "latest" values, rather than some that were written a few hundred cycles back.

Also, do note that if this code is truly that performance-critical, it's not as if even completely non-synchronized access is free. As soon as you share cache-lines between processors, you're going to have cache-synchronization traffic between the CPUs in question, and while I haven't benchmarked it or anything, I suspect adding volatile to the equation wouldn't make much of a difference. The latency of communicating with other CPUs to change the state of a cache-line is probably a bigger issue than avoiding memory barriers in one CPU's instruction stream.

In order to avoid any of these penalties, you may want to do something like having a class for sharing the stats with other threads, separate from the "real" counters, which you only update once every, say, 10,000 updates to the real counters. That way, you could also do volatile access to that shared class without incurring any regular penalty to the writer thread.

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
  • Your observation is to the point. It does not harm if numbers are slightly out of date. That's why I started to get interested in the question if there is some cheap (and dirty) middle way between your proposed 'so what' and the 100% correct solution. – Harald Mar 16 '16 at 13:49
  • 1
    @Harald, The thing to remember when reading this answer is that Java is supposed to "run anywhere." That means that the Java Language Spec (JLS) does not make promises that can't be kept on _every_ reasonable platform. There might be some platform out there that can implement your "half-sided" consistency with good performance, but there might also be some otherwise-desirable platform that can't implement it. The JLS aims for the lowest common denominator in those cases. – Solomon Slow Mar 16 '16 at 15:00
0

Check the Java 5 collection and concurrency classes, they provide a higher level of abstraction above the language primitives. In your example an AtomicLong should work as desired.

Thomas
  • 11,272
  • 2
  • 24
  • 40
  • I know all these classes. My question is not how to implement this. I am trying to get a better understanding of the happens-before relation and whether there is one here or not. – Harald Mar 16 '16 at 13:35
  • 1
    then read the source of how `AtomicLong` works, that will give you your answer if you can't understand what the specifications say about this. –  Mar 16 '16 at 13:45
0

You could refactor your class as:

import java.util.concurrent.atomic.AtomicLong;

public class Stats {

    private final AtomicLong successes = new AtomicLong();
    private final AtomicLong failures = new AtomicLong();

    public long success() {
        return successes.incrementAndGet();
    }

    public long failed() {
        return failures.incrementAndGet();
    }

    public void logStats() {
        final long s = successes.get();
        final long f = failures.get();
        // do stuffs with s and f
    }
}

Then you don't have to care about concurrency, since the classes in java.util.concurrent.atomic has only atomic (then thread safe) methods.

Francesco Pitzalis
  • 2,042
  • 1
  • 16
  • 20
0

No, it is not enough to make only method logStats to be synchronized. In your case you should use atomic counter to prevent race conditions, which are very likely to appear under high contention. Or use locks/synchronization in all methods, which is a bit overkill for counter problem

Cootri
  • 3,806
  • 20
  • 30