当前位置: 首页 > 知识库问答 >
问题:

使用Java的算法可以被认为是非阻塞的吗?

袁青青
2023-03-14

我试图用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操作原子化?

共有1个答案

东方乐
2023-03-14

[免责声明]在我明确写下这篇文章之前,我的发言是关于一般的算法,而不是考虑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(),则将正确绘制结果,但执行将被