为什么非阻塞算法不能正常工作
Why non-blocking algorithm does not work correctly
我正在写一个线程安全的计数器。当我测试时,线程是第一个,然后是第二个,一切正常。但是当线程同时进入increment()方法时,计数器就不能正常工作了。原因不清楚,我用的是原子整数。
public class CASCount {
private final AtomicReference<Integer> count = new AtomicReference<>(0);
private AtomicInteger oldValue = new AtomicInteger(0);
private AtomicInteger newValue = new AtomicInteger(0);
public void increment() {
do {
oldValue.set(count.get());
System.out.println(oldValue + " old");
if (oldValue.get() == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
newValue.incrementAndGet();
System.out.println(newValue + " new");
} while (!count.compareAndSet(oldValue.get(), newValue.get()));
}
public int get() {
int result = -1;
result = count.get();
if (result == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
return result;
}
}
@Test
public void whenUseCASCount() throws InterruptedException {
CASCount count = new CASCount();
Thread one = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("one");
count.increment();
}
});
Thread two = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("two");
count.increment();
}
});
one.start();
two.start();
one.join();
two.join();
assertThat(count.get(), is(10));
}
TL;DR - 让你的 increment
方法 synchronized
.
详细信息 - 即使您使用了 atomic
个变量,不 意味着您的 class 是线程安全的。它不安全,因为变量的检查和增量之间可能存在(并且确实存在)竞争条件。
do {
oldValue.set(count.get());
System.out.println(oldValue + " old");
if (oldValue.get() == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
newValue.incrementAndGet(); <--- between here
System.out.println(newValue + " new");
} while (!count.compareAndSet(oldValue.get(), newValue.get())); <--- and here
check-then-act竞争条件的典型案例。
发生这种情况是因为您的原子变量可以被多个线程访问,并且它们的共享状态可以从一个线程发生变化而在另一个线程中看不到。
To preserve state consistency, update related state variables in a single
atomic operation.
- Java Concurrency in Practice
因此,我们使用内在锁(内置synchronized
)来确保方法在多个线程访问时是安全的。发生的情况是原子变量的状态不会改变,因为每个线程将一次访问一个 increment
方法。
这是我的决定
private final AtomicReference<Integer> count = new AtomicReference<>(0);
public void increment() {
int current, next;
do {
current = count.get();
next = current + 1;
} while (!count.compareAndSet(current, next));
}
public int get() {
return count.get();
}
我正在写一个线程安全的计数器。当我测试时,线程是第一个,然后是第二个,一切正常。但是当线程同时进入increment()方法时,计数器就不能正常工作了。原因不清楚,我用的是原子整数。
public class CASCount {
private final AtomicReference<Integer> count = new AtomicReference<>(0);
private AtomicInteger oldValue = new AtomicInteger(0);
private AtomicInteger newValue = new AtomicInteger(0);
public void increment() {
do {
oldValue.set(count.get());
System.out.println(oldValue + " old");
if (oldValue.get() == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
newValue.incrementAndGet();
System.out.println(newValue + " new");
} while (!count.compareAndSet(oldValue.get(), newValue.get()));
}
public int get() {
int result = -1;
result = count.get();
if (result == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
return result;
}
}
@Test
public void whenUseCASCount() throws InterruptedException {
CASCount count = new CASCount();
Thread one = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("one");
count.increment();
}
});
Thread two = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("two");
count.increment();
}
});
one.start();
two.start();
one.join();
two.join();
assertThat(count.get(), is(10));
}
TL;DR - 让你的 increment
方法 synchronized
.
详细信息 - 即使您使用了 atomic
个变量,不 意味着您的 class 是线程安全的。它不安全,因为变量的检查和增量之间可能存在(并且确实存在)竞争条件。
do {
oldValue.set(count.get());
System.out.println(oldValue + " old");
if (oldValue.get() == -1) {
throw new UnsupportedOperationException("Count is not impl.");
}
newValue.incrementAndGet(); <--- between here
System.out.println(newValue + " new");
} while (!count.compareAndSet(oldValue.get(), newValue.get())); <--- and here
check-then-act竞争条件的典型案例。
发生这种情况是因为您的原子变量可以被多个线程访问,并且它们的共享状态可以从一个线程发生变化而在另一个线程中看不到。
To preserve state consistency, update related state variables in a single atomic operation.
- Java Concurrency in Practice
因此,我们使用内在锁(内置synchronized
)来确保方法在多个线程访问时是安全的。发生的情况是原子变量的状态不会改变,因为每个线程将一次访问一个 increment
方法。
这是我的决定
private final AtomicReference<Integer> count = new AtomicReference<>(0);
public void increment() {
int current, next;
do {
current = count.get();
next = current + 1;
} while (!count.compareAndSet(current, next));
}
public int get() {
return count.get();
}