AtomicReference element for array and array changes visibility

Does Java guarantee that updates to array elements performed by thread A , before storing an array reference in AtomicReference will always be displayed in thread B that receives this link

In other words, what are the possible outcomes of doing this:

 class References { AtomicReference<String[]> refs = new AtomicReference<>(new String[]{"first"}); public void add(String s) { refs.updateAndGet(oldRefs -> { String[] newRefs = new String[oldRefs.length + 1]; System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length); newRefs[oldRefs.length] = s; return newRefs; }); } public static void main(String[] args) { References r = new References(); new Thread(() -> r.add("second")).start(); System.out.println(Arrays.toString(r.refs.get())); } } 

can only output those that are [first] or [first, second] , or can you also get results such as [first, null] or [null, null] ?

The javadoc for java.util.concurrent.atomic states:

compareAndSet and all other read and update operations, such as getAndIncrement , have memory effects for both reading and writing volatile variables.

which, it seems, does not provide any guarantees for non-volatile array elements, only the array reference itself.

+6
source share
3 answers

Volatile writing to a variable ensures that everything that happened before it happens before the next volatile reading of the same variable .

Relevant rules from JLS :

17.4.4. Sync order

  • Writing to the variable v (Β§8.3.1.4) is synchronized with all subsequent readings of v by any thread (where the "next" is determined in accordance with the synchronization order).

17.4.5. It happens - before the order

Two actions can be ordered using an occur-to relationship. If one action occurs - in front of another, then the first is visible and ordered before the second.

If we have two actions x and y, we write hb (x, y) to indicate that x occurs before y.

  • If x and y are actions of the same thread, and x goes to y in program order, then hb (x, y).
  • If the action x is synchronized with the next action y, then we also have hb (x, y).

  • If hb (x, y) and hb (y, z), then hb (x, z).

How AtomicReference guarantees that the link to your array will only be stored / loaded in mutable mode (and after writing you will not modify the existing array), this is enough to guarantee that the results of System.arrayCopy() (and the line following it) are visible to everyone who calls refs.get() .

However, the design is still not completely waterproof , because anyone who gets an array reference through refs.get() can go on and make changes to elements without AtomicReference protection.

CopyOnWriteArrayList works very similar to this (it uses a combination of ReentrantLock and volatile array fields instead of AtomicReference ), except that it is also guaranteed that no one can get the underlying array and play with it in an unsafe manner.

+5
source

you cannot get the null element in the array because you did not change the elements of the array in your code, but each time you create a new one ( String[] newRefs = new String[oldRefs.length + 1]; ). Since you store a reference to an array and do not change its elements, you cannot see null elements. If your code has something like:

  refs.updateAndGet(oldRefs -> { if (oldRefs.size>0) oldRefs[0]=null;//is an example just for "fun" String[] newRefs = new String[oldRefs.length + 1]; System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length); newRefs[oldRefs.length] = s; return newRefs; }); 

then you can see something else, and some users can see a null value in the first element

UPDATE : since AtomicReference only works for array references, you can use AtomicReferenceArray for more secure access to array elements

+1
source

An AtomicReference for an array is no different from any other - it is only a reference that is atomic and therefore has corresponding memory barriers. Access to the array is similar to any other object - without additional protection.

This way you always get [first] or [first, second] , and when you add a new array and add it to an old link that is atomically protected, you cannot use another option.

Java arrays are not very good at resizing. If you want to resize the structure, you better use an ArrayList . If you need concurrent access to it, use CopyOnWriteArrayList , which is essentially what you are trying to implement in your code.

+1
source

All Articles