Might sound a little silly but I'm not proficient in Java so wanted to make sure:
If there are two code points
I:
if (_myVar == null)
{
return;
}
II:
synchronized (_myLock)
{
_myVar = MyVarFactory.create(/* real params */)
}
Edit: Assuming that this _myVar
is a complex object (i.e. not boolean, int, or long) but a fully fledged java class that has some parent classes etc. .
Assuming that I and II can run on separate threads at the same time, I think that in C++ this would be a "data race", however I am not sure about the situation in Java.
Even though the variable is not currently being written to, previous writes to the variable may not yet be visible to all threads. This means two threads can read the same value and get different results creating a race condition.
The proper value will be written atomically as a single operation, and threads will read the current value as a single atomic operation as well, even if another thread is writing. So for integers, you're safe on most architectures.
TL;DR: No, not okay.
Explanation:
The relevant documentation is the Java Memory Model (JMM).
The JMM gives the freedom to the JVM to make a local cached copy of every field on all objects for each individual thread.
Then, it hands each thread a coin. Anytime the thread reads a field or writes a field, it flips this coin. On heads, it uses its local cache. On tails, it updates both its local cache as well as the 'real' copy.
Furthermore, the coin is evil. It is not actually random, but it is unreliable. It may flip tails every time today, every time on the test machine, and every time during the first week of the beta. And then just when you're giving a demo to that important potential customer it starts flipping heads on you, reliably, all day, every time. Just.. all of a sudden.
The name of the game is simple: If the behaviour of your program depends on the result of the evil coin flip, you lose.
Thus, either write code that doesn't care (hard), or write code that suppresses the flips (easier).
In general, the easiest thing to do is to never have any fields that you concurrently write to and read from. This sounds impossible but is, in fact, quite easy: Top-down frameworks like fork join do all communications via the stack (so, method parameter passing and method return values), and there is of course that old, tried, and true trick: Do all comms via a channel that has excellent support for concurrent operations, such as a relational database like postgres, or a message queue like rabbitmq.
If you must use the same field from multiple threads in a concurrent fashion, the only way to ensure the evil coin is not flipped is to establish so-called 'Happens-Before/Happens-After' relationships (this is the official terminology as used in the JMM): There are certain specific ways to set up a relationship such that the JMM officially blesses 2 lines of code: That line will definitely 'happen after' that line (which means: The line that 'happens after' will definitely observe the changes that were caused by the line that 'happens before'). Without HBHA, evil coin flip occurs and you may or may not see the change depending on the phase of the moon.
The list of HBHA causation is lengthy, but the common ways:
thread.start()
is guaranteed to happen-before the first line of code within that thread.synchronized
: If a thread exits a synchronized block, then that happens-before any other thread entering a synchronized block that is synchronizing on the same object reference.In your code example, there is absolutely no HBHA going on, as I assume that the first snippet runs in one thread and the second snippet runs in another. Yes, the second snippet uses synchronized
, but the first does not, and synchronized
can only establish HBHA with other synchronized
blocks (and only if they are synchronizing on the exact same object). Thus, you have no HBHA.
Therefore, the JMM gives the JVM the freedom to run your snippets such that you do not observe that update done by the second snippet (where _myVar
is set up to some instance), even if it CAN observe other stuff that the second thread did change.
SOLUTION: Set up HBHA; use either an AtomicReference
which does it for you, or toss a synchronized(_myLock)
around the first snippet, or forget this and use a db or rabbitmq or fork/join or some other framework.
NB: There is pretty much no way to write tests that confirm that evil coin flips are occurring. You should take the advice to look into obviating the need to talk about sharing mutating fields between threads entirely with e.g. fork/join, message queues, or databases seriously as a consequence: Multithreaded code that shares fields has a tendency to be riddled with bugs that no tests can catch.
No, not thread-safe.
Data race is only one problem. You likely have an issue with CPU core cache visibility.
If your variable is of type boolean, int, or long (or the object equivalent), use the appropriate Atomic…
class. These classes wrap their content for thread-safe access.
If not of those types, use AtomicReference
to contain your object in a thread-safe manner.
There are other techniques too, besides the Atomic…
classes.
All this has been covered many times on Stack Overflow. So search to learn more.
And read the classic book, Java Concurrency in Practice by Brian Goetz, et al.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With