Подтвердить что ты не робот

Безопасность потока java-массива

Существуют ли какие-либо проблемы concurrency с одним потоком, считываемым из одного индекса массива, тогда как другой поток записывает в другой индекс массива, если индексы различны?

например. (этот пример не обязательно рекомендуется для реального использования, только для иллюстрации моей точки)

 class Test1
 {
     static final private int N = 4096;
     final private int[] x = new int[N];
     final private AtomicInteger nwritten = new AtomicInteger(0);
     // invariant: 
     // all values x[i] where 0 <= i < nwritten.get() are immutable

     // read() is not synchronized since we want it to be fast
     int read(int index) {
         if (index >= nwritten.get())
             throw new IllegalArgumentException();
         return x[index];
     }
     // write() is synchronized to handle multiple writers
     // (using compare-and-set techniques to avoid blocking algorithms
     // is nontrivial)
     synchronized void write(int x_i) {
         int index = nwriting.get();
         if (index >= N)
             throw SomeExceptionThatIndicatesArrayIsFull();
         x[index] = x_i;
         // from this point forward, x[index] is fixed in stone
         nwriting.set(index+1);
     }     
 }

изменить: критиковать этот пример не является моим вопросом, я буквально просто хочу знать, имеет ли доступ к массиву один индекс, одновременно доступ к другому индексу, создает проблемы concurrency, не может думать простого примера.

4b9b3361

Ответ 1

Хотя вы не получите недопустимое состояние при изменении массивов, как вы упомянули, у вас будет та же проблема, которая возникает, когда два потока просматривают энергонезависимое целое число без синхронизации (см. Раздел "Учебное руководство по Java по ошибкам согласованности памяти"). По сути, проблема в том, что Поток 1 может записать значение в пространство i, но нет никакой гарантии, когда (или если) Поток 2 увидит изменение.

Класс java.util.concurrent.atomic.AtomicIntegerArray делает то, что вы хотите сделать.

Ответ 2

В примере есть много вещей, которые отличаются от вопроса прозы.

Ответ на этот вопрос заключается в том, что отдельные элементы массива обращаются независимо друг от друга, поэтому вам не нужна синхронизация, если два потока меняют разные элементы.

Однако модель памяти Java не дает никаких гарантий (что я знаю), что значение, написанное одним потоком, будет видимым для другого потока, если вы не синхронизируете доступ.

В зависимости от того, что вы действительно пытаетесь выполнить, вероятно, что java.util.concurrent уже имеет класс, который сделает это за вас. И если это не так, я по-прежнему рекомендую взглянуть на исходный код для ConcurrentHashMap, так как ваш код, похоже, делает то же самое, что и для управления хэш-таблицей.

Ответ 3

Я не уверен, что синхронизация только метода write, оставив метод read нехитронированным, будет работать. На самом деле это не все последствия, но по крайней мере это может привести к тому, что метод read вернет некоторые значения, которые только что были переопределены write.

Ответ 4

Да, поскольку неправильное чередование кеша может происходить в среде с несколькими процессорами/ядрами. Есть несколько вариантов, чтобы избежать этого:

  • Используйте небезопасную Sun-private библиотеку для атомарного набора элемента в массиве (или добавленную функцию jsr166y в Java7
  • Использовать массив AtomicXYZ []
  • Использовать настраиваемый объект с одним изменчивым полем и иметь массив этого объекта.
  • Используйте ParallelArray из jsr166y addendum вместо этого в вашем алгоритме

Ответ 5

Поскольку read() не синхронизирован, вы можете иметь следующий сценарий:

Thread A enters write() method
Thread A writes to nwriting = 0;
Thread B reads from nwriting =0;
Thread A increments nwriting. nwriting=1
Thread A exits write();

Поскольку вы хотите гарантировать, что ваши переменные адреса никогда не конфликтуют, что-то вроде (проблемы с дисконтированием массива):

int i;
synchronized int curr(){ return i; }
synchronized int next(){ return ++i;}

int read( ) {
         return values[curr()];
     }

void write(int x){
   values[next()]=x;
}