在下面的代码中:我正在更新num[1]=0的AtomicIntegerArray num,每个线程在两个线程中更新1000次。
在主线程中的两个线程的末尾;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);也不起作用。
发布于 2016-11-02 02:11:25
我得到了< 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);accumulateAndGet(int i,int x,IntBinaryOperator accumulatorFunction)
原子地更新索引i处的元素,结果是将给定的函数应用于当前和给定的值,并返回更新的值。该函数应该是无副作用的,因为当尝试的更新由于线程之间的争用而失败时,它可能会被重新应用。函数以索引I处的当前值作为其第一个参数,而给定的update作为第二个参数。
发布于 2016-11-02 02:14:10
这是一个典型的种族状况。任何时候,当您有一个提取,一个操作,和一个put,您的代码是动态的。
考虑两个线程,它们都在大致“同一时间”执行num.set(1,num.get(1)+1)。首先,让我们详细分析表达式本身所做的事情:
num.get(1);让我们称其为xy尽管表达式中的中间值只是堆栈上的值,而不是显式变量,但操作是相同的: 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逻辑,直到它工作,而没有其他人在操作之间进行竞争”。在上面的示例中,线程B会注意到compareAndSet(1, 5, 6)失败(因为当时的实际值是6,而不是预期的5),因此重新尝试了。事实上,所有这些原子方法,比如getAndIncrement,都是这样做的。
https://stackoverflow.com/questions/40371265
复制相似问题