2

在下面的代码中:我在 2 个线程中更新num[1]=0AtomicIntegerArraynum 1000 次。

在主线程中的 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);
            }

        }

    }

}

编辑:添加num.compareAndSet(1, num.get(1), num.get(1)+1);而不是也num.set(1,num.get(1)+1);不起作用。

4

2 回答 2

1

我得到随机值 < 2000。有人能告诉我为什么吗?

这称为丢失更新问题

因为,在下面的代码中:

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

累积和获取(int i,int x,IntBinaryOperator accumulatorFunction)

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

于 2016-11-02T02:11:25.337 回答
1

这是一个经典的比赛条件。任何时候你有一个 fetch、一个操作和一个 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),或以原子方式执行 get、add 和 put 的类似方法之一。

compareAndSet这些方法实际上都可以构建在您确定的方法之上。但要做到这一点,您需要在循环中进行增量,尝试直到compareAndSet返回 true。此外,为了使其工作,您将该初始num.get(1)值存储在局部变量中,而不是第二次获取它。实际上,这个循环说“继续尝试 get-add-put 逻辑,直到它在没有其他人在操作之间竞争的情况下工作。” 在我上面的示例中,线程 B 会注意到compareAndSet(1, 5, 6)失败(因为当时的实际值是 6,而不是预期的 5),因此重试。这实际上是所有这些原子方法(如getAndIncrement)所做的。

于 2016-11-02T02:14:10.187 回答