Рассмотрим следующий код:
public static void main(String[] args) throws InterruptedException {
int nThreads = 10;
MyThread[] threads = new MyThread[nThreads];
AtomicReferenceArray<Object> array = new AtomicReferenceArray<>(nThreads);
for (int i = 0; i < nThreads; i++) {
MyThread thread = new MyThread(array, i);
threads[i] = thread;
thread.start();
}
for (MyThread thread : threads)
thread.join();
for (int i = 0; i < nThreads; i++) {
Object obj_i = array.get(i);
// do something with obj_i...
}
}
private static class MyThread extends Thread {
private final AtomicReferenceArray<Object> pArray;
private final int pIndex;
public MyThread(final AtomicReferenceArray<Object> array, final int index) {
pArray = array;
pIndex = index;
}
@Override
public void run() {
// some entirely local time-consuming computation...
pArray.set(pIndex, /* result of the computation */);
}
}
Каждый MyThread вычисляет что-то полностью локально (без необходимости синхронизации с другими потоками) и записывает результат в указанную c ячейку массива. Основной поток ожидает завершения всех MyThreads, а затем извлекает результаты и что-то с ними делает.
Использование методов get
и set
для AtomicReferenceArray
обеспечивает порядок памяти, который гарантирует, что основной поток увидит результаты, записанные MyThreads.
Однако, поскольку каждая ячейка массива записывается только один раз, и никакой MyThread не должен видеть результат, написанный любым другим MyThread, мне интересно, действительно ли эти гарантии строгого упорядочения необходимо или если следующий код, с доступом к ячейкам простого массива, всегда будет давать те же результаты, что и код выше:
public static void main(String[] args) throws InterruptedException {
int nThreads = 10;
MyThread[] threads = new MyThread[nThreads];
Object[] array = new Object[nThreads];
for (int i = 0; i < nThreads; i++) {
MyThread thread = new MyThread(array, i);
threads[i] = thread;
thread.start();
}
for (MyThread thread : threads)
thread.join();
for (int i = 0; i < nThreads; i++) {
Object obj_i = array[i];
// do something with obj_i...
}
}
private static class MyThread extends Thread {
private final Object[] pArray;
private final int pIndex;
public MyThread(final Object[] array, final int index) {
pArray = array;
pIndex = index;
}
@Override
public void run() {
// some entirely local time-consuming computation...
pArray[pIndex] = /* result of the computation */;
}
}
С одной стороны, при доступе в простом режиме компилятор или среда выполнения могут произойдет оптимизация доступа к чтению к array
в конечном l oop основного потока и заменит Object obj_i = array[i];
на Object obj_i = null;
(неявная инициализация массива), так как массив не изменяется внутри этого потока. С другой стороны, я где-то читал, что Thread.join
делает все изменения присоединенного потока видимыми для вызывающего потока (что было бы разумно), поэтому Object obj_i = array[i];
должен видеть ссылку на объект, назначенную i
-ым MyThread .
Итак, последний код даст те же результаты, что и выше?