2016-11-02 1 views
1

Я обновляю num[1]=0 из AtomicIntegerArray num 1000 раз в 2 потоках.Data Races в AtomicIntegerArray

В конце 2 нитей в основной нити не должно быть значение num[1] равно 2000, так как не должно быть расчётов данных в AtomicIntegerArray.

Однако я получаю случайные значения < 2000. Может ли кто-нибудь сказать мне, почему?

Код:

import java.util.concurrent.atomic.AtomicIntegerArray; 

public class AtomicIntegerArr { 

    private static AtomicIntegerArray num= new AtomicIntegerArray(2); 

    public static void main(String[] args) throws InterruptedException { 
     Thread t1 = new Thread(new MyRun1()); 
     Thread t2 = new Thread(new MyRun2()); 

     num.set(0, 10); 
     num.set(1, 0); 

     System.out.println("In Main num before:"+num.get(1)); 

     t1.start(); 
     t2.start(); 

     t1.join(); 
     t2.join(); 

     System.out.println("In Main num after:"+num.get(1)); 
    } 

    static class MyRun1 implements Runnable { 
     public void run() { 
      for (int i = 0; i < 1000; i++) { 
       num.set(1,num.get(1)+1); 
      } 

     } 
    } 

    static class MyRun2 implements Runnable { 
     public void run() { 
      for (int i = 0; i < 1000; i++) { 
       num.set(1,num.get(1)+1); 
      } 

     } 

    } 

} 

Edit: Добавление num.compareAndSet(1, num.get(1), num.get(1)+1); вместо num.set(1,num.get(1)+1); не работает либо.

ответ

0

Я получаю случайные значения < 2000. Может ли кто-нибудь сказать мне, почему?

Это называется the lost-update problem.

Так, в следующем коде:

num.set(1, num.get(1) + 1); 

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

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

int v; 
do { 
    v = num.get(1); 
} while (!num.compareAndSet(1, v, v+1)); 

Там же метод именно для этой цели:

num.accumulateAndGet(1, 1, (x, d)->x+d); 

accumulateAndGet (INT I, Int х, IntBinaryOperator accumulatorFunction)

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

+0

Привет, Люк: Да, вся операция была не атомной! Виноват !!! Что касается его решения, я знал о закодированном CompareAndSet, но не знал о методе accumulateAndGet. Использование GetAndIncrement тоже решило! Благодаря :) !! –

1

Это классическое состояние гонки. Каждый раз, когда у вас есть выборка, операция и пометка, ваш код является ярким.

Рассмотрите два потока, выполняющих num.set(1,num.get(1)+1) примерно в то же время. Во-первых, давайте сломаем то, что делает само выражение:

  • it fetches num.get(1); назовем это x
  • добавляет 1 к этому; назовем это y
  • он ставит эту сумму в `num.set (1, y);

Несмотря на то, что промежуточные значения в выражении представляют собой только значения в стеке, а не явные переменные, операция такая же: get, add, put.

Хорошо, поэтому вернемся к двум нашим потокам. Что делать, если операции упорядочены так?

inital state: n[1] = 5 
Thread A  | Thread B 
======================== 
x = n[1] = 5 | 
       | x = n[1] = 5 
       | y = 5 + 1 = 6 
y = 5 + 1 = 6 | 
n[1] = 6  | 
       | n[1] = 6 

Поскольку обе нити принесли значение до того, как нить добавила его добавленную стоимость, они оба делают то же самое. У вас есть 5 + 1 дважды, а результат - 6, а не 7!

Что вы хотите, это getAndIncrement(int idx), или один из методов, который дает получение, добавление и помещение атомарно.

Эти методы могут быть фактически созданы на основе идентифицированного вами метода compareAndSet. Но для этого вам нужно сделать инкремент в цикле, пытаясь до тех пор, пока compareAndSet не вернет true. Кроме того, для этого вам нужно сохранить начальное значение num.get(1) в локальной переменной, вместо того, чтобы извлекать его во второй раз. Фактически, этот цикл говорит: «Продолжайте использовать логику get-add-put до тех пор, пока он не будет работать, если кто-либо еще не побежит между операциями». В приведенном выше примере Thread B заметил бы, что compareAndSet(1, 5, 6) терпит неудачу (поскольку фактическое значение в это время равно 6, а не 5, как ожидалось) и, таким образом, повторено. Это на самом деле то, что делают все эти атомные методы, например getAndIncrement.

+1

num.set (1, num.get (1) +1); Я был под впечатлением, что он будет атомически полностью, что верно, но до того, как на самом деле был вызван набор; другой get может быть вызван во втором потоке, и тогда могут появиться 2 набора. getandIncrement действительно получает, увеличивает и устанавливает атомарно, тогда как в моем случае это может возникнуть (произойдет, снова произойдет, а затем 2 набора). Спасибо Ишавит :) –

Смежные вопросы