可以创建可以原子交换的 AtomicReference 吗?

发布于 2024-10-14 13:51:19 字数 1865 浏览 2 评论 0原文

有没有办法实现一种引用类型,其值可以与另一种原子交换?


在 Java 中,我们有 AtomicReference,它可以与局部变量交换,但不能与另一个 AtomicReference 交换。

您可以执行以下操作:

AtomicReference r1 = new AtomicReference("hello");
AtomicReference r2 = new AtomicReference("world");

并通过两个操作的组合来交换它们:

r1.set(r2.getAndSet(r1.get()));

但这会使它们之间处于不一致的状态,其中都包含 "hello"。此外,即使您可以原子地交换它们,您仍然无法原子地读取它们(作为一对)。


我希望能够做的是:

PairableAtomicReference r1 = new PairableAtomicReference("hello");
PairableAtomicReference r2 = new PairableAtomicReference("world");
AtomicRefPair rp = new AtomicRefPair(r1, r2);

然后

Object[] oldVal, newVal;
do {
    oldVal = rp.get();
    newVal = new Object[] {oldVal[1], oldVal[0]};
} while (! rp.compareAndSet(oldVal, newVal));

交换值,并在另一个线程中:

AtomicRefPair otherRP = new AtomicRefPair(r1, r2);
System.out.println(Arrays.toString(otherRP.get()));

并确保输出为 [hello, world][world,你好]

注意:

  • r1r2 在此操作中配对,但另一个线程可能会独立配对,例如 r1 和另一个 r3 (不幸的是,这意味着我无法使用 这个解决方案。)
  • 将会有数十万个这样的引用,因此全局ReentrantLock将是一个主要瓶颈。
  • rpotherRP 不一定在线程之间共享,因此简单地锁定它们是行不通的。它们可以实习,但实习生池需要自己的同步,这将是另一个瓶颈。
  • 我在这里只制作了 2 个参考文献的分组,但是能够将 3 个或更多的参考文献分组将是一个额外的好处。

是否可以实现 AtomicRefPair 的无锁版本?我有预感,但如果不是,那么也许有一篇文章解释了原因?


相关如何在 C# 中自动交换 2 个整数?

Is there any way to implement a type of reference whose value can be exchanged with another atomically?


In Java we have AtomicReference which can be swapped with a local variable but not with another AtomicReference.

You can do:

AtomicReference r1 = new AtomicReference("hello");
AtomicReference r2 = new AtomicReference("world");

and swap them with a combination of two operations:

r1.set(r2.getAndSet(r1.get()));

But this leaves them in an inconsistent state in between, where both contain "hello". Also even if you could swap them atomically, you still could not read them (as a pair) atomically.


What I would like to be able to do is:

PairableAtomicReference r1 = new PairableAtomicReference("hello");
PairableAtomicReference r2 = new PairableAtomicReference("world");
AtomicRefPair rp = new AtomicRefPair(r1, r2);

then

Object[] oldVal, newVal;
do {
    oldVal = rp.get();
    newVal = new Object[] {oldVal[1], oldVal[0]};
} while (! rp.compareAndSet(oldVal, newVal));

to swap the values, and in another thread:

AtomicRefPair otherRP = new AtomicRefPair(r1, r2);
System.out.println(Arrays.toString(otherRP.get()));

and be certain that the output will be either [hello, world] or [world, hello].

Notes:

  • r1 and r2 are paired for this operation, but it's possible that another thread will independently pair, say r1 and another r3 (unfortunately that means I cannot use this solution.)
  • There will be hundreds of thousands of these references, so a global ReentrantLock would be a major bottleneck.
  • rp and otherRP are not necessarily shared between threads, so simply locking them will not work. They could be interned, but the intern pool would need its own synchronization which would be another bottleneck.
  • I have only made groups of 2 references here, but the ability to group 3 or more would be a bonus.

Is it possible to implement a lock-free version of AtomicRefPair? I have a hunch that it isn't, but if not then maybe there is an article somewhere that explains why?


Related: How do I atomically swap 2 ints in C#?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

萌化 2024-10-21 13:51:19

有一个不可变的类来保存该对。那是你的原子。交换对意味着替换原子。

更新:你的问题不是很清楚。但一般来说,对于由多个变量组成的并发系统,人们可能需要

  1. 拍摄系统状态的快照。快照一旦拍摄就不会改变。
  2. 通过一次更改多个变量来自动更新系统状态。可能需要在我的更新和以前的快照(我的计算所基于的)之间没有其他更新

,如果它不消耗太多资源,您可以直接在快照中对系统进行建模。

Have an immutable class holding the pair. That is your atom. Swapping the pair means replacing the atom.

update: your question isn't very clear. but in general, for a concurrent system consisting of multiple variables, one might want

  1. take a snapshot of system state. the snapshot doesn't change once taken.
  2. atomically update system state by changing multiple variables at once. it may be required that there is no other update between my update an a previous snapshot (which my calculation was based on)

you can model your system directly in snapshots, if it doesn't consume too much resources.

莳間冲淡了誓言ζ 2024-10-21 13:51:19

我不知道是否有一个好的解决方案,但以下丑陋的解决方案可以工作:

public final class MyReference<T> extends ReentrantLock implements Comparable<MyReference<T>> {
    public MyReference() {
        id = counter.incrementAndGet();
    }

    public void swap(MyReference<T> other) {
        if (id < other.id) {
            lock();
            other.lock();
        } else {
            other.lock();
            lock();
        }
        final T tmp = value;
        value = other.value;
        other.value = tmp;
        unlock();
        other.unlock();
    }

    public static <T> List<T> consistentGet(List<MyReference<T>> references) {
        final ArrayList<MyReference<T>> sortedReferences = Lists.newArrayList(references);
        Collections.sort(sortedReferences);
        for (val r : sortedReferences) r.lock();
        final List<T> result = Lists.newArrayListWithExpectedSize(sortedReferences.size());
        for (val r : references) result.add(r.value);
        for (val r : sortedReferences) r.unlock();
        return result;
    }

    @Override
    public int compareTo(MyReference<T> o) {
        return id < o.id ? -1 : id > o.id ? 1 : 0;
    }

    private final static AtomicInteger counter = new AtomicInteger();

    private T value;
    private final int id;
}
  • 使用 MyReference 而不是 AtomicReference。
  • 它使用了很多锁,但没有一个是全局的。
  • 它以固定的顺序获取锁,因此不会出现死锁。
  • 它使用 lombok 和 guava 进行编译(将其视为没有它们的伪代码)。

I don't know if there's a nice solution, but the following ugly one could work:

public final class MyReference<T> extends ReentrantLock implements Comparable<MyReference<T>> {
    public MyReference() {
        id = counter.incrementAndGet();
    }

    public void swap(MyReference<T> other) {
        if (id < other.id) {
            lock();
            other.lock();
        } else {
            other.lock();
            lock();
        }
        final T tmp = value;
        value = other.value;
        other.value = tmp;
        unlock();
        other.unlock();
    }

    public static <T> List<T> consistentGet(List<MyReference<T>> references) {
        final ArrayList<MyReference<T>> sortedReferences = Lists.newArrayList(references);
        Collections.sort(sortedReferences);
        for (val r : sortedReferences) r.lock();
        final List<T> result = Lists.newArrayListWithExpectedSize(sortedReferences.size());
        for (val r : references) result.add(r.value);
        for (val r : sortedReferences) r.unlock();
        return result;
    }

    @Override
    public int compareTo(MyReference<T> o) {
        return id < o.id ? -1 : id > o.id ? 1 : 0;
    }

    private final static AtomicInteger counter = new AtomicInteger();

    private T value;
    private final int id;
}
  • Use MyReference instead of AtomicReference.
  • It uses a lot of locks, but none of them is global.
  • It acquires locks in a fixed order, so it's deadlock-free.
  • It compiles using lombok and guava (take it as pseudocode without them).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文