【问题标题】:Data Races in an AtomicIntegerArrayAtomicIntegerArray 中的数据竞争
【发布时间】:2016-11-02 01:47:17
【问题描述】:

在下面的代码中: 我在 2 个线程中更新 num[1]=0AtomicIntegerArray num 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); 也不起作用。

【问题讨论】:

    标签: java multithreading concurrency atomicreference


    【解决方案1】:

    我得到的随机值

    这叫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 处的当前值作为其第一个参数,并将给定的更新作为第二个参数。

    【讨论】:

    • 嗨,卢克:是的,整个操作不是原子的!我的错 !!!至于解决它,我知道循环的 CompareAndSet 但不知道可用的累积和获取方法。使用 GetAndIncrement 也解决了它!谢谢:) !!
    【解决方案2】:

    这是一个经典的比赛条件。任何时候你有一个 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)所做的。

    【讨论】:

    • num.set(1,num.get(1)+1);我的印象是 set 将完全以原子方式运行,这是真的,但在实际调用 set 之前;可以在第二个线程中调用另一个 get ,然后可以发生 2 组。 getandIncrement 确实以原子方式获取、递增和设置;而在我的情况下;这可能会出现(发生;再次发生,然后是 2 组)。谢谢 Yshavit :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2015-12-27
    • 2013-08-12
    相关资源
    最近更新 更多