ntkachov
ntkachov

Reputation: 1232

Concurrent read/write to a variable in java

If I have a variable from which multiple threads read and only one thread writes, do I need to have a lock around that variable? Would it crash if one thread tries to read and the other thread tries to write at the same time?

Upvotes: 13

Views: 8945

Answers (3)

Steve B.
Steve B.

Reputation: 57284

The concurrency concern is not crashing, but what version of the data you're seeing.

  • if the shared variable is written atomically, it's possible for one (reader) thread to read a stale value when you thought your (writer) thread had updated the variable. You can use volatile keywords to prevent your reader threads from reading a stale value in this situation.

  • if the write operation is not atomic (for example if it's a composite object of some kind and you're writing bits of it at a time, while the other threads could theoretically be reading it) then your concern would also be that some reader threads could see the variable in an inconsistent state. You'd prevent this by locking access to the variable while it was being written (slow) or making sure that you were writing atomically.

Writes to some types of fields are atomic but without a happens-before relationship that ensures correct memory ordering (unless you use volatile); see this page for details.

Upvotes: 13

Alex Miller
Alex Miller

Reputation: 70211

The simple answer is yes, you need synchronization.

If you ever write to a field and read it from anywhere else without some form of synchronization, your program can see inconsistent state and is likely wrong. Your program will not crash but can see either the old or new or (in the case of longs and doubles) half old and half new data.

When I say "some form of synchronization" though, I more precisely mean something that creates a "happens-before" relationship (aka memory barrier) between the write and read locations. Synchronization or java.util.concurrent.lock classes are the most obvious way to create such a thing, but all of the concurrent collections typically also provide similar guarantees (check the javadoc to be sure). For example, doing a put and take on a concurrent queue will create a happens-before relationship.

Marking a field as volatile prevents you from seeing inconsistent references (long-tearing) and guarantees that all threads will "see" a write. But volatile fields writes/reads cannot be combined with other operations in larger atomic units. The Atomic classes handle common combo ops like compare-and-set or read-and-increment. Synchronization or other java.util.concurrent synchronizers (CyclicBarrier, etc) or locks should be used for larger areas of exclusivity.

Departing from the simple yes, there are cases that are more "no, if you really know what you're doing". Two examples:

1) The special case of a field that is final and written ONLY during construction. One example of that is when you populate a pre-computed cache (think of a Map where keys are well-known values and values are pre-computed derived values). If you build that in a field prior to construction and the field is final and you never write to it later, the end of the constructor performs "final field freeze" and subsequent reads DO NOT need to synchronize.

2) The case of the "racy single check" pattern which is covered in Effective Java. The canonical example is in java.lang.String.hashCode(). String has a hash field that is lazily computed the first time you call hashCode() and cached into the local field, which is NOT synchronized. Basically, multiple threads may race to compute this value and set over other threads, but because it is guarded by a well-known sentinel (0) and always computes the identical value (so we don't care which thread "wins" or whether multiple do), this actually is guaranteed to be ok.

A longer reference (written by me): http://refcardz.dzone.com/refcardz/core-java-concurrency

Upvotes: 5

Joshua Kaplan
Joshua Kaplan

Reputation: 883

Be aware that volatile is NOT atomic, which means that double and long which use 64 bits can be read in an inconsistent state where 32 bits are the old value and 32 bits are the new value. Also, volatile arrays do not make the array entries volatile. Using classes from java.util.concurrent is strongly recommended.

Upvotes: 2

Related Questions