首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AtomicIntegerArray中的数据竞争

AtomicIntegerArray中的数据竞争
EN

Stack Overflow用户
提问于 2016-11-02 01:47:17
回答 2查看 292关注 0票数 2

在下面的代码中:我正在更新num[1]=0AtomicIntegerArray num,每个线程在两个线程中更新1000次。

在主线程中的两个线程的末尾;num[1]的值不应该是2000年,因为在AtomicIntegerArray中不应该有数据竞争。

但是,我得到了< 2000的随机值。谁能告诉我原因吗?

代码:

代码语言:javascript
复制
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);也不起作用。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-11-02 02:11:25

我得到了< 2000的随机值。谁能告诉我原因吗?

这叫做丢失更新问题

因为,在以下代码中:

代码语言:javascript
复制
num.set(1, num.get(1) + 1);

虽然所涉及的每个单独操作都是原子操作,但组合操作不是。来自两个线程的单个操作可以交错,导致来自一个线程的更新被另一个线程用陈旧的值覆盖。

您可以使用compareAndSet来解决这个问题,但是您必须检查操作是否成功,并在操作失败时再次执行。

代码语言:javascript
复制
int v;
do {
    v = num.get(1);
} while (!num.compareAndSet(1, v, v+1));

为了达到这个目的,也有一种方法:

代码语言:javascript
复制
num.accumulateAndGet(1, 1, (x, d)->x+d);

accumulateAndGet(int i,int x,IntBinaryOperator accumulatorFunction)

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

票数 1
EN

Stack Overflow用户

发布于 2016-11-02 02:14:10

这是一个典型的种族状况。任何时候,当您有一个提取,一个操作,和一个put,您的代码是动态的。

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

  • 它获取num.get(1);让我们称其为x
  • 它增加了1;让我们称其为y
  • 它把这个和放在`num.set(1,y);

尽管表达式中的中间值只是堆栈上的值,而不是显式变量,但操作是相同的: get、add、put。

好吧,回到我们的两条线上。如果手术是这样安排的呢?

代码语言:javascript
复制
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逻辑,直到它工作,而没有其他人在操作之间进行竞争”。在上面的示例中,线程B会注意到compareAndSet(1, 5, 6)失败(因为当时的实际值是6,而不是预期的5),因此重新尝试了。事实上,所有这些原子方法,比如getAndIncrement,都是这样做的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40371265

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档