Java并发编程实战中谈到过ABA问题,这里摘录下:
---------------------------------------------------------------
ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这种问题 (主要在没有垃圾回收机制的环境中)。在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作。 在大多数情况下,包括这本书给出的示例,这种判断是完全足够的。然而,有时候还需要知道“自从上次看到V的值为A以来, 这个值是否发生了变化?”在某些算法中,如果V的值首先由A变成B,再由B变成A,那么仍然被认为是发生了变化, 并需要重新执行算法中的某些步骤。
解决办法就是“版本号”
---------------------------------------------------------------
我们来看一下StampedLock的官方例子,
import java.util.concurrent.locks.StampedLock;
//
public class Point {
//
private double x, y;
private final StampedLock sl = new StampedLock();
//
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
//
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
//
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp); //升级为写锁
if (ws != 0L) { //upgrade成功
stamp = ws;
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
//
public static void main(String[] args) {
Point point = new Point();
point.moveIfAtOrigin(10, 20);
point.distanceFromOrigin();
}
}
move方法中writeLock返回long,这个stamp就是版本号。
distanceFromOrigin方法中tryOptimisticRead是乐观读,源码中可知,实际没有lock。 然后validate会校验stamp是否变化。
其中U.loadFence();是关键,作用是 强制读取操作和验证操作在一些情况下的内存排序问题。
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}
//
public boolean validate(long stamp) {
U.loadFence();
return (stamp & SBITS) == (state & SBITS);
}
这里有篇文章,在官方文档的基础上加入自己的理解,特分享下。 http://lianming.info/java8/2014/02/10/stamped-lock/
---------------------------------------------------------------
到这里了还没有结束,JaonHui有篇文章《Bug:StampedLock的中断问题导致CPU爆满》 http://ifeve.com/stampedlock-bug-cpu/
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.StampedLock;
//
public class TestStampedLock {
public static void main(String[] args) throws InterruptedException {
final StampedLock lock = new StampedLock();
new Thread() {
public void run() {
long readLong = lock.writeLock();
LockSupport.parkNanos(6100000000L);
lock.unlockWrite(readLong);
}
}.start();
Thread.sleep(100);
for (int i = 0; i < 3; ++i)
new Thread(new OccupiedCPUReadThread(lock)).start();
System.out.println("end");
}
//
private static class OccupiedCPUReadThread implements Runnable {
private StampedLock lock;
//
public OccupiedCPUReadThread(StampedLock lock) {
this.lock = lock;
}
//
public void run() {
Thread.currentThread().interrupt();
long lockr = lock.readLock();
System.out.println(Thread.currentThread().getName()
+ " get read lock");
lock.unlockRead(lockr);
}
}
}
这个例子说的是对中断的不作为。作者JaonHui给出了修复,见这里: https://github.com/zuai/Hui/blob/master/StampedLock.java