Vanishing Volatile: Threading and References

Posted November 15, 2019 by Brett Schuchert

Overview

Recently we came across a need to use volatile. At the time my spider senses were tingling; I wasn’t sure. We confirmed that it was in fact necessary. Then we improved the implementation a touch, which removed the need to use the keyword.

The Beginning

I joined a mob while it was in the middle of updating some inherited multi-threaded code. More on that in a forthcoming blog, but in summary:

  • One thread existed to capture log output from an application running in the cloud.
  • Another thread waited on values in that log. It started everything, waited for the system under test to handle a message, publish a message, and then waited for a result.
  • The group I was with didn’t write the original solution, so while there were some automated tests, we were back-filling missing tests.

As I looked at the code collecting the log lines, it looked something like:

public void track(String logLines) {
    this.logs += logLines + '\n';
}

This code is inefficient, to be sure, but when I saw that, and realized that another thread was looking at the collected value, I was pretty certain that we needed the mark the field as volatile:

private volatile String logs = "";

This lucky intuition came about because I read Concurrent Programming in Java back when it was published.

We got the test passing and then “verified” that volatile was needed by seeing the test fail without it and pass with it.

Time, as usual, to the rescue

Later that evening I realized that the hunch I had was nearly correct. I was thinking in terms of values rather than refernces. String values cannot change. In this particular case the issue was a reference changing versus its value because its value could not change.

The volatile keyword informs java to not assume values are fixed. The thread waiting for the value, checking it every so often, cached the value of the reference the first time it ran, but never checked the value of the reference again. The other thread changes the value of the reference every chance it gets:

Snapshots in Time

At Time T1, the LogGobbler points to an empty string. As the main thread starts, it picks up that reference, and caches it.

Also at T1, the gobbler thread starts and points to the exact same object (a String in memory).

The main thread checks the log values, waiting for a signal to that it is OK to continue. However, since the thread assumes the underlying value of the field isn’t changing, it keeps checking the original value.

Once logs start flowing, the gobbler thread picks up all the available lines and appends them to its internal field. Since strings are immutable, the code actually creates a new string and sets the field to the new string.

At Time T2, the main thread isn’t paying attention because it has cached a reference to the original, empty, string.

At Time T3 and going forward, nothing really changes.

So as written, the code requires volatile.

But do it better

String concatenation is inefficient. In this particular case efficiency doesn’t matter. However, in addition to being inefficient, it makes things like threading a bit more of an issue.

Collecting strings in a StringBuilder is a better general solution. As it turns out, it also resolves the need for the volatile keyword.

Now the field looks like this:

private StringBuilder logs = new StringBuilder();

Since each thread uses the same reference, and also, since that reference does not change, there’s no need to mark this reference as volatile

Volatile Vanished

Now both threads look at the same object, a StringBuilder, whose reference never changes, but its internals change as one thread appends logs to it. 

Conclusion

I was pleasantly surprised that I guessed right for the need to use volatile. While I wasn't spot on, I was close enough to be dangerous.

Improving the implementation also and simplified the solution and removed the need for volatile altogether.

In this case, a String is more like a primitive than a object. Something needs to change (we're collecting information). A String (the collector) is immutable. Thus, create a new string to have an updated value. In functional programming, this is a blessing. In stateful OO programming, it's an unfortunate characteristics (context and all that).

The StringBuffer is a better implementation and it somewhat moves away from border-line primitive obsession. I'd even go a bit further and say that this is a micro version of the dependency inversion principle as well, but that's for as as yet even later blog, or maybe an earlier one.