SergeiK
SergeiK

Reputation: 345

Can threads in Java cache a final field?

Suppose I declared the following array:

private final int[] array = new int[10];

Now, if I start 10 threads each writing a value to its own key (Thread 1 writes to array[0], Thread 2 writes to array[1], etc) will any thread try to cache array or all changes will be made in the main memory?

Upvotes: 1

Views: 258

Answers (3)

Peter Lawrey
Peter Lawrey

Reputation: 533750

A thread can cache any non-volatile value.

In particular, code for a thread can inline into code a boolean value that thread doesn't change. The thread might not detect a change until it is recompiled.

In this case, both the array reference and the values in the array will be cached, but most likely will see the new value after some time.

The use of final makes no real difference. One reason is that reflection allows you to change final fields.

A cleaner way to have a thread safe array like this is to use AtomicIntegerArray added in Java 5.0

final AtomicIntegerArray array = new AtomicIntegerArray(10);

public void increment(int n) {
    array.incrementAndGet(n);
}

Upvotes: 2

Tamas Rev
Tamas Rev

Reputation: 7164

Update

The final modifier makes sure all the Thread-s can see the initialized value of that reference. This is important: the final keyword refers only to the array, but not to the values stored within that array.

Original answer

We don't know what is going to happen. The JVM might or might not cache the values. I suggest not to build any dependency on this.

I think you will want some visibility. In general, you can force visibility with either the volatile keyword or with synchronization. In this case you have an array if integers.

An array of AtomicInteger-s

You can replace it with array of AtomicInteger -s:

private final AtomicInteger[] array = new AtomicInteger[10];

But in this case you'll have to manipulate the values somewhat differently:

array[0] = new AtomicInteger(0);
array[0].incrementAndGet();

A concurrent list

Another approach is to use a concurrent list, like CopyOnWriteArrayList or use the Collections.synchronizedList() wrapper method:

// option 1
private final List<Integer> list = Collections.synchronizedList(new ArrayList<>());

// option 2
private final List<Integer> list = new CopyOnWriteArrayList<>();

Either way, you'll have somewhat different accessors than those of an array. The List initialization is the most tricky part:

// this prevents all kinf of OutOfBoundsException-s
for (int i = 0; i < 10; i++) {
    list.add(0);
}
// and now you can use it
list.set(0, list.get(0) + 1);

A concurrent map

You can also use map, so you can skip the problematic initialization:

private final ConcurrentMap<Integer, Integer> map = new ConcurrentHashMap<>();
// ...
map.putIfAbsent(0, 0);
map.put(0, map.get(0) + 1);

Upvotes: 1

Sleiman Jneidi
Sleiman Jneidi

Reputation: 23339

No, final has no visibility semantics for individual array elements, even a volatile would only guarantee a happens before order on the reference mutation itself not the elements. For that you would need to synchronize or use a CopyOnWriteArrayList instead.

Upvotes: 1

Related Questions