2011-01-25 44 views
6

有没有什么办法可以实现一种参考类型的值可以与另一个原子交换?可能创建可以原子交换的AtomicReference?


在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#?

+1

Guava中有一个Interner,它使用ConcurrentHashMap,所以争用可以是平均任意小的。 – maaartinus 2011-01-25 22:44:27

回答

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和番石榴编译(把它当作没有它们的伪代码)。
+0

+1。几乎我想提出的建议。然而,运营商需要“成千上万的这些参考资料”。也许最好将它们分组到分区中,并将锁与这些分区关联起来,这几乎与`ConcurrentHashMap`的工作方式一样。如果分区数量相当大,争用的概率应该很低。 – axtavt 2011-01-25 22:34:22

+0

我不会,因为ReentrantLock只包含一个对一个空对象的引用,所以它很小。所以即使其中一百万只需要24 MB(假设每个参考8B和每个对象16B)。 – maaartinus 2011-01-25 22:41:20

4

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

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

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

如果不占用太多资源,则可以在快照中直接建模您的系统。