AtomicIntegerArray 中的数据竞争
Data Races in an AtomicIntegerArray
在下面的代码中:
我在 2 个线程中每个更新 num[1]=0
个 AtomicIntegerArray
num 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);
也不起作用。
I get random values < 2000. Could someone tell me why?
因为,在下面的代码中:
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)
Atomically updates the element at index i with the results of applying the given function to the current and given values, returning the updated value. The function should be side-effect-free, since it may be re-applied when attempted updates fail due to contention among threads. The function is applied with the current value at index i as its first argument, and the given update as the second argument.
这是一个典型的竞争条件。任何时候你有一个获取、一个操作和一个放置,你的代码都是活泼的。
考虑两个线程,它们都在大约 "same time." 时执行 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)
,或者以原子方式执行获取、添加和放置的类似方法之一。
这些方法实际上都可以建立在您确定的 compareAndSet
方法之上。但是要做到这一点,您需要在一个循环中进行递增,尝试直到 compareAndSet
returns 为真。此外,为了使其起作用,您已将初始 num.get(1)
值存储在局部变量中,而不是再次获取它。实际上,此循环表示 "keep trying the get-add-put logic until it works without anyone else having raced between the operations." 在我上面的示例中,线程 B 会注意到 compareAndSet(1, 5, 6)
失败(因为当时的实际值为 6,而不是预期的 5),因此会重试。这实际上是所有那些原子方法,如 getAndIncrement
,所做的。
在下面的代码中:
我在 2 个线程中每个更新 num[1]=0
个 AtomicIntegerArray
num 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);
也不起作用。
I get random values < 2000. Could someone tell me why?
因为,在下面的代码中:
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)
Atomically updates the element at index i with the results of applying the given function to the current and given values, returning the updated value. The function should be side-effect-free, since it may be re-applied when attempted updates fail due to contention among threads. The function is applied with the current value at index i as its first argument, and the given update as the second argument.
这是一个典型的竞争条件。任何时候你有一个获取、一个操作和一个放置,你的代码都是活泼的。
考虑两个线程,它们都在大约 "same time." 时执行 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)
,或者以原子方式执行获取、添加和放置的类似方法之一。
这些方法实际上都可以建立在您确定的 compareAndSet
方法之上。但是要做到这一点,您需要在一个循环中进行递增,尝试直到 compareAndSet
returns 为真。此外,为了使其起作用,您已将初始 num.get(1)
值存储在局部变量中,而不是再次获取它。实际上,此循环表示 "keep trying the get-add-put logic until it works without anyone else having raced between the operations." 在我上面的示例中,线程 B 会注意到 compareAndSet(1, 5, 6)
失败(因为当时的实际值为 6,而不是预期的 5),因此会重试。这实际上是所有那些原子方法,如 getAndIncrement
,所做的。