AtomicIntegerArray中的数据竞争

时间:2016-11-02 01:47:17

标签: java multithreading concurrency atomicreference

在下面的代码中: 我在2个帖子中每次更新num[1]=0AtomicIntegerArray次1000次。

在主线程中的2个线程结束时; num[1]的值不应该是2000,因为AtomicIntegerArray中不应该存在数据争用。

然而,我得到随机值<有人可以告诉我为什么吗?

代码:

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);
            }

        }

    }

}

修改:添加num.compareAndSet(1, num.get(1), num.get(1)+1);代替num.set(1,num.get(1)+1);也不起作用。

2 个答案:

答案 0 :(得分:1)

这是一个经典的竞争条件。无论何时你有一个获取,一个操作和一个put,你的代码都是活泼的。

考虑两个线程,它们大致在“同一时间”执行num.set(1,num.get(1)+1)。首先,让我们分解表达式本身正在做的事情:

  • 取出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逻辑,直到它工作,没有其他人在操作之间进行竞争。”在上面的例子中,线程B会注意到compareAndSet(1, 5, 6)失败(因为那时的实际值是6,而不是预期的5),因此重试。事实上,所有这些原子方法,如getAndIncrement,都是如此。

答案 1 :(得分:0)

  

我得到随机值&lt;有人可以告诉我为什么吗?

这称为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 x,IntBinaryOperator accumulatorFunction)

  

以索引i的形式原子更新元素,并将给定函数应用于当前值和给定值,并返回更新后的值。该函数应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时,它可能会被重新应用。该函数应用索引i的当前值作为其第一个参数,并将给定的更新作为第二个参数。