I am confused about sharing arrays safely between threads in Java, specifically memory fences and the keyword synchronized
.
This Q&A is helpful, but does not answer all of my questions: Java arrays: synchronized + Atomic*, or synchronized suffices?
What follows is sample code to demonstrate the issue. Assume there is a pool of worker threads that populates the SharedTable
via method add(...)
. After all worker threads are done, a final thread reads and saves the data.
Sample code to demonstrate the issue:
public final class SharedTable { // Column-oriented data entries private final String[] data1Arr; private final int[] data2Arr; private final long[] data3Arr; private final AtomicInteger nextIndex; public SharedTable(int size) { this.data1Arr = new String[size]; this.data2Arr = new int[size]; this.data3Arr = new long[size]; this.nextIndex = new AtomicInteger(0); } // Thread-safe: Called by worker threads public void addEntry(String data1, int data2, long data3) { final int index = nextIndex.getAndIncrement(); data1Arr[index] = data1; data2Arr[index] = data2; data3Arr[index] = data3; } // Not thread-safe: Called by clean-up/joiner/collator thread... // after worker threads are complete public void save() { // Does this induce a full memory fence to ensure thread-safe reading of synchronized (this) { final int usedSide = nextIndex.get(); for (int i = 0; i < usedSide; ++i) { final String data1 = data1Arr[i]; final int data2 = data2Arr[i]; final long data3 = data3Arr[i]; // TODO: Save data here } } } }
The sample code above could also be implemented using Atomic*Array
, which acts as an “array of volatile values/references”.
public final class SharedTable2 { // Column-oriented data entries private final AtomicReferenceArray<String> data1Arr; private final AtomicIntegerArray data2Arr; private final AtomicLongArray data3Arr; private final AtomicInteger nextIndex; public SharedTable2(int size) { ... } // Thread-safe: Called by worker threads public void addEntry(String data1, int data2, long data3) { final int index = nextIndex.getAndIncrement(); data1Arr.set(index, data1); ... } // Not thread-safe: Called by clean-up/joiner/collator thread... // after worker threads are complete public void save() { final int usedSide = nextIndex.get(); for (int i = 0; i < usedSide; ++i) { final String data1 = data1Arr.get(i); final int data2 = data2Arr.get(i); final long data3 = data3Arr.get(i); // TODO: Save data here } } }
- Is
SharedTable
thread-safe (and cache coherent)? - Is
SharedTable
(much?) more efficient as only a single memory fence is required, whereasSharedTable2
invokes a memory fence for each call toAtomic*Array.set(...)
?
If it helps, I am using Java 8 on 64-bit x86 hardware (Windows and Linux).
Advertisement
Answer
No, SharedTable is not thread-safe. A happens-before is only guaranteed if you read, from a synchronized block, something that has been written from a synchronized block using the same lock.
Since the writes are made out of a synchronized block, the JMM doesn’t guarantee that the writes will be visible by the reader thread.