Tóm Tắt
3 Tips for volatile fields in java
Volatile fields are one of built-in mechanism to write multi-threaded java.
Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread. … The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the variable become visible to B after reading the volatile variable.
— Java Concurrency in Practice – Brian Goetz, et al.
In the following I collected three tips on when and how to use volatile fields in practice:
1) Use volatile fields when writes do not depend on its current value.
An example is a flag to stop a worker thread from another thread:
public class WorkerThread extends Thread { private volatile boolean isRunning = true; @Override public void run() { while(isRunning) { // execute a task } } public void stopWorker() { isRunning = false; } }
The WorkerThread executes his tasks in a while loop, line 5. It checks the volatile field isRunning in each iteration and stops processing if the field is false. This allows other threads to stop the WorkerThread by calling the method stopWorker which sets the value of the field to false. Since a thread can call the method stopWorker even if the WorkerThread is already stopped, the write to the field can be executed independently of its current value.
By declaring the field volatile we make sure that the WorkerThread sees the update done in another Thread and does not run forever.
2) Use volatile fields for reading and locks for writing
The java.util.concurrent.CopyOnWriteArrayList get and set methods are an example of this tip:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } private E get(Object[] a, int index) { return (E) a[index]; } public E get(int index) { return get(getArray(), index); } public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } } // Other fields and methods omitted }
The get method, line 13, simply reads the volatile field array and returns the value at the position index. Writing uses a lock to ensure that only one thread can modify the array at a given time. Line 18 acquires the lock and line 33 releases the lock. Writing requires copying the array when an element is changed, line 24 so that the reading threads do not see an inconsistent state. The writing thread then updates the array, line 25 and set the new array to the volatile field array, line 26.
Using this tip only writes block writes. Compare this to using synchronized set and get methods where each operation block all other operations. Or java.util.concurrent.locks.ReentrantReadWriteLock where too many readers can lead to starvation of writers.
This is especially a problem for older JDKs. Here are the number from Akhil Mittal in a DZone comment to this article:
Java 6 RO= 4, RW= 4, fair=false 4,699,560 584,991 Java 9 RO= 4, RW= 4, fair=false 2,133,904 3,289,220
3) Use with JDK 9 VarHandle for atomic operations.
All modern CPU provide instructions to atomically compare and set or increment and get values. Those operations are used internally by the JVM to implement synchronized locks. Prior to JDK 1.9, they were available for Java applications only through classes in the java.util.concurrent.atomic package or by using the private java API sun.misc.Unsafe. With the new JDK 9 VarHandle, it is now possible to execute such operations directly on volatile fields. The following shows the AtomicBoolean compareAndSet method implemented using VarHandles:
public class AtomicBoolean implements java.io.Serializable { private static final VarHandle VALUE; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); VALUE = l.findVarHandle(AtomicBoolean.class, "value", int.class); } catch (ReflectiveOperationException e) { throw new Error(e); } } private volatile int value; public final boolean compareAndSet(boolean expectedValue, boolean newValue) { return VALUE.compareAndSet(this, (expectedValue ? 1 : 0), (newValue ? 1 : 0)); } // Other fields and methods omitted }
The VarHandle works similar to the class java.lang.reflect.Field. You need to lookup a VarHandle from the class which contains the field using the name of the field, line 6. To execute a compareAndSet operation on the field we need to call the VarHandle with the object of the field, the expected and the new value, line 13.
Conclusion
You can use volatile fields if the writes do not depend on the current value as in tip one. Or if you can make sure that only one thread at a time can update the field as in tip two and three. I think that those three tips cover the more common ways to use volatile fields. Read here about a more exotic way to use volatile fields to implement a concurrent queue.
I would be glad to hear from you about other ways to use volatile fields to achieve thread-safety.