在下面的代码中:
我在2个帖子中每次更新num[1]=0
次AtomicIntegerArray
次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);
也不起作用。
答案 0 :(得分:1)
这是一个经典的竞争条件。无论何时你有一个获取,一个操作和一个put,你的代码都是活泼的。
考虑两个线程,它们大致在“同一时间”执行num.set(1,num.get(1)+1)
。首先,让我们分解表达式本身正在做的事情:
num.get(1)
;我们称之为x
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;有人可以告诉我为什么吗?
因为,在以下代码中:
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的当前值作为其第一个参数,并将给定的更新作为第二个参数。