我尝试使用Java实现非阻塞二进制搜索树,如here所述。该算法基于单世界CAS,但是:
状态和信息 字段一起存储在CAS对象中。因此,一个内部 节点使用四个字的内存。
我决定使用 AtomicStampedReference 来存储这些数据。问题是它
通过创建表示" boxed"的内部对象来维护标记的引用。 [参考,整数]对。
此结构的内部实施:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
对定义为:
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
此实施是否仍然无阻塞?
还有一个额外的问题:是什么让这个 compareAndSet 操作成为原子?
答案 0 :(得分:5)
[免责声明] 在我明确写这篇文章之前,我的陈述是关于算法的,而不是考虑Java CAS的实现。
是的,在我看来,基于CAS的算法可以被认为是无锁的。 Wikipedia提供了无锁算法的定义:
在计算机科学中,非阻塞算法可确保竞争共享资源的线程不会因互斥而无限期推迟执行。如果无论调度如何都能保证系统范围内的进度,则非阻塞算法无锁;
...
因此,在现代使用中,如果一个或多个线程的暂停不会停止剩余线程的潜在进度,则算法非阻塞。
让我们看一下那个上下文中基于CAS的算法[说算法我的意思是使用带有while循环的CAS指令设置变量]:
问题一:基于CAS的算法是非阻塞的吗?在每个特定时刻,都有一个成功CAS变量的线程。如果我们暂停其中一个,其余的将取而代之。因此,该算法满足我引用的定义,答案是肯定的。
问题二:基于CAS的算法是否无锁?我认为答案是肯定的,因为系统范围内,但不是每个线程的进度(每个线程最终会成功继续)变量)是有保证的。
现在,让我们考虑在Java中使用AtomicXXX
类和CAS操作在其对象上实现基于CAS的算法。从技术上讲,无法保证此类算法的无锁定性由于JVM方面可能产生的外部影响:
public class Entity {
static {
new Thread(){
@Override
public void run() {
synchronized (getClass().getClassLoader()){
try {
getClass().getClassLoader().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
AtomicReference v = new AtomicReference(new Object());
Object var = null;
Object somenew;
do {
var = v.get();
somenew = new Object();
} while(!v.compareAndSet(var, somenew));
}
}
main()
中实现的算法是无锁的,但由于类加载器的监视器没有得到通知,因此不会有系统范围的进度。 那么,我刚才写的是什么?基于cas的算法在Java中无锁是因为它们是基于cas的事实是错误的。
CAS指令的定义是原子的。 在大多数现代CPU 中支持此指令,即执行我们期望它执行的操作并以原子方式执行此操作。假设CPU供应商保证CPU支持原子CAS。
Wikipedia引用:
自1970年以来,Compare-and-Swap(以及Compare-and-Swap-Double)已成为IBM 370(以及所有后续)架构中不可或缺的一部分。
截至2013年,大多数多处理器架构都在硬件中支持CAS。
JDK的一些证明:
AtomicInteger.compareAndSet()
Javadoc州:
原子如果当前值==预期值,则将值设置为给定的更新值。
同样可以在AtomicXXX
课程中找到,你可以轻松找到它,所以不值得在这里进行复制。
现在,让我们考虑AtomicStampedReference.compareAndSet()
。其Javadoc说:
如果当前引用= =预期引用且当前标记等于预期标记,则以原子方式将引用和标记的值设置为给定的更新值。
我认为从javadoc我们不能得出结论整个操作是原子的,它只是设置该值的原子,因此设置了stamp和reference两者,或者它们都没有作为CAS失败。< / p>