At the most basic level it has to be said that a lack of volatile
doesn't guarantee that it will fail. It just means that the JVM is allowed to do optimizations that could lead to failure. But whether those optimizations happen and whether they then lead to failure is influenced by many different factors. Therefore it's often very hard to actually detect these problems, until they become catastrophic.
For a starter, I'd like to summarize conditions that happen to frequently coincide when it does go wrong.
- the non-volatile variable is usually read in a tight loop
- the non-volatile variable is changed rarely, but when it changes it's "important" in some sense.
- the amount of code executed inside that loop is small (roughly small enough to be fully inlined by an aggressive compiler)
- the tight loop over-running has a very visible effect (for example it leads to an exception and not just silently doing unnecessary work).
Note that not all of those are necessary, but they tend to be true when I actually observe the issue.
My personal interpretation (plus some reading on the topic) lead me to these rules of thumb:
- if reading the wrong value won't be noticed, then you simply won't notice if the volatile is missing. If the only bad thing that happens is that you run through a loop a couple of times unnecessarily, then chances are you will never realize that it happens.
- when the reads of the volatile variable happen with enough "distance" between them (where distance is measured by other read access to other parts of memory) then it can often behave as if it was volatile, simply because it drops out of the cache
- any kind of synchronization on anything inside the loops tends to have the effect of invalidating some caches at least and thus causes the variable to act as if it was volatile.
These three alone make it rather hard to actually spot the problem except in very extreme cases (i.e. when executing once too many causes a big crash in your system).
In your specific example, I assume that the feature flag is not something that will be toggled multiple times per second. It's more likely that it's set once per process and then stays untouched.
For example, if you have multiple incoming requests in the same second and halfway through the second you toggle the feature flag it can happen that some of the requests that happen after the toggling will still use the old value, due to having it cached from earlier.
Will you notice? Unlikely. It'll be extremely hard to distinguish "this request came in just before the change" from "this request came in just after the change and wrongly used the old value". If 6 out of 10 requests use the old value instead of the correct 5 out of 10, there's a good chance no one will ever notice.