我试图用Java实现这里描述的非阻塞二叉查找树。该算法基于单世界CAS,但是:
状态和信息字段一起存储在 CAS 对象中。因此,内部节点使用四个字的内存。
我决定使用AtomicStampedReference来存储这些数据。问题是它
通过创建表示“盒装”[引用,整数] 对的内部对象来维护标记引用。
该结构的内部实施:
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操作原子化?
[免责声明]在我明确写下这篇文章之前,我的发言是关于一般的算法,而不是考虑Java CAS实现。
是的,在我看来基于CAS的算法可以认为是无锁的。维基百科提供了无锁算法的定义:
在计算机科学中,非阻塞算法确保竞争共享资源的线程不会因互斥而无限期推迟执行。如果不管调度如何都有保证的系统范围的进度,则非阻塞算法是无锁的;
...
因此,在现代用法中,如果一个或多个线程的挂起不会停止其余线程的潜在进程,则算法是非阻塞的。
让我们看看在这种情况下基于CAS的算法[说算法我的意思是使用CAS指令设置变量,同时循环]:
问题一:基于CAS的算法是非阻塞的吗?在每一个特定时刻,都有一个线程将成功实现CAS变量。如果我们暂停其中一个,剩下的一个将成功。因此,算法满足我引用的定义,答案是肯定的。
问题二:基于CAS的算法是无锁的吗?我认为答案又是肯定的,因为系统范围的,但不是每个线程的进度(每个线程最终都会成功cas-ing变量)是有保证的。
现在,让我们考虑在 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));
}
}
在< code>main()中实现的算法是无锁的,但是由于类装入器的监视器从来没有得到通知,所以不会有系统范围的进展。那么,我刚才到底写了什么?仅仅因为基于cas的算法在Java中是无锁的,这种说法是错误的。
CAS指令根据其定义是原子的。在大多数现代CPU中,这种指令是受支持的,也就是说,做我们期望它做的事情,并且是自动完成的。比方说,CPU厂商保证CPU支持原子CAS。
维基百科引用:
自1970年以来,比较和交换(以及比较和交换双精度)一直是IBM 370(以及所有后继者)体系结构不可或缺的一部分。
截至2013年,大多数多处理器架构在硬件上支持CAS。
JDK的一些证明:
AtomicInteger.compareAndSet()
Javadoc声明:
如果当前值==预期值,则原子地将值设置为给定的更新值。
在 AtomicXXX
类上也可以找到相同的内容,您可以轻松找到它,因此不值得在此处复制粘贴。
现在,让我们考虑AtomicStampedReference.compareAndSet()
。它的Javadoc说:
如果当前引用==预期引用并且当前标记等于预期标记,则原子地将引用和标记的值设置为给定的更新值。
我认为从javadoc中我们不能断定整个操作是原子性,它只是原子性地设置值,所以要么同时设置stamp和reference,要么都不设置,因为CAS会失败。
问题内容: 我想使用redis的pubsub传输一些消息,但不想使用阻止,例如以下代码: 最后一部分将被阻止。我只想检查给定频道中是否有数据,该如何完成?有没有类似的方法? 问题答案: 我认为那不可能。通道没有任何“当前数据”,您订阅了一个通道并开始接收该通道上其他客户端推送的消息,因此它是一个阻塞的API。另外,如果您查看pub / sub 的Redis Commands文档,将会更加清楚。
我有一个顶点,它有一个处理程序,可以在事件循环线程中调用Vertx的Web客户端。实际的底层API调用是同步的还是异步的?它会阻塞我的事件循环线程吗?假设我的API调用需要30秒才能返回。 我是否需要用Vertx.execute阻塞(p-
问题内容: 我的印象是,通过SQLAlchemy进行的数据库调用将被阻止,不适合用于除同步代码以外的任何其他内容。我是正确的吗(我希望不是!),或者是否可以将其配置为非阻塞状态? 问题答案: 您可以使用gevent以非阻塞样式使用SQLA。这是使用psycopg2和psycopg2的协程支持的示例: https://bitbucket.org/zzzeek/green_sqla/ 我也听说人们在p
问题内容: 我想编写一个可以同时写入多个文件的程序。认为可以通过使用非阻塞模式在一个线程中实现。但是FileChannel不支持非阻塞模式。有人知道为什么吗? 问题答案: UNIX不支持非阻塞的文件I / O,看到非阻塞I / O与常规文件 。由于Java应该(至少尝试在所有平台上)提供相同的行为,因此不会实现。 但是,Java 7将包括一个支持 异步 文件I / O 的新类,这是与非阻塞I /
问题内容: 我想使用打开管道,并对其具有非阻塞的“读取”访问权限。 我该如何实现? (我发现的示例都是阻塞/同步的) 问题答案: 设置如下: 现在您可以阅读: 完成后,清理:
问题内容: 最近几天,我一直在与Numpy和matplotlib一起玩。我在尝试使matplotlib绘制函数而不阻止执行时遇到问题。我知道这里已经有很多线程在问类似的问题,并且我已经在Google上搜索了很多,但是没有设法使这项工作有效。 我曾尝试按照某些人的建议使用show(block = False),但是我得到的只是一个冻结的窗口。如果我简单地调用show(),则将正确绘制结果,但执行将被