问题描述
我正在编写一个线程安全计数器。当我测试并且线程首先运行时,然后第二个一切正常。但是当线程同时进入 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));
}
解决方法
我的决定
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();
}
,
TL;DR - 使您的 increment
方法 synchronized
。
详细信息 - 尽管您使用了 atomic
变量,但这并不不意味着您的类是线程安全的。这是不安全的,因为您的变量的检查和增量之间可能存在(并且存在)竞争条件。
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 竞争条件的典型案例。
发生这种情况是因为您的原子变量可以被多个线程访问,并且它们的共享状态可以从一个线程发生变化而在另一个线程中看不到。
为了保持状态一致性,在单个更新相关状态变量 原子操作。
- Java 并发实践
因此,当多个线程访问该方法时,我们使用内在锁(内置 synchronized
)使该方法安全。发生的情况是原子变量的状态不会改变,因为每个线程一次访问一个 increment
方法。