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

时间:2011-01-25 21:31:25

标签: java interlocked atomicreference atomic-swap

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


在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, hello]

注意:

  • r1r2配对进行此操作,但另一个线程可能会独立配对,比如r1和另一个r3(不幸的是,这意味着我无法使用this solution。)
  • 这些引用将有数十万个,因此全局ReentrantLock将成为主要瓶颈。
  • rpotherRP不一定在线程之间共享,因此只需锁定它们就行不通。它们可能是interned,但实习池需要自己的同步,这将是另一个瓶颈。
  • 我这里只提供了2个参考小组,但是将3个或更多组合的能力将是一个奖励。

是否可以实现AtomicRefPair的无锁版本?我有预感它不是,但如果没有,那么可能有一篇文章可以解释为什么?


相关How do I atomically swap 2 ints in C#?

2 个答案:

答案 0 :(得分:4)

拥有一对不可变的类。那是你的原子。交换对意味着更换原子。

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

  1. 拍摄系统状态的快照。快照一旦拍摄就不会改变。
  2. 通过一次更改多个变量来原子更新系统状态。可能要求我的更新与之前的快照(我的计算所基于的)之间没有其他更新
  3. 如果不消耗太多资源,您可以直接在快照中为系统建模。

答案 1 :(得分:3)

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

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进行编译(不带它们作为伪代码)。