2011-01-25 6 views
6

¿Hay alguna manera de implementar un tipo de referencia cuyo valor se pueda intercambiar con otro atómicamente?Posible crear AtomicReference que se puede intercambiar atómicamente?


En Java tenemos AtomicReference que puede ser intercambiado con una variable local, pero no con otro AtomicReference.

Usted puede hacer:

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

e intercambiarlos con una combinación de dos operaciones:

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

Pero esto les deja en un estado inconsistente en el medio, donde ambos contienen "hello". Además, incluso si pudieras intercambiarlos atómicamente, aún no podrías leerlos (como un par) atómicamente.


Lo que me gustaría ser capaz de hacer es:

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

continuación

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

para cambiar los valores, y en otro hilo:

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

y asegúrese de que la salida sea [hello, world] o [world, hello].

Notas:

  • r1 y r2 están emparejados para esta operación, pero es posible que otro hilo se asociará de forma independiente, dicen r1 y otro r3
  • Hay (por desgracia eso significa que no puedo usar this solution). serán cientos de miles de estas referencias, por lo que un ReentrantLock global sería un cuello de botella importante.
  • rp y otherRP no son necesariamente compartidos entre subprocesos, por lo tanto, simplemente bloquearlos no funcionará. Podrían ser interned, pero el grupo interno necesitaría su propia sincronización, que sería otro cuello de botella.
  • Solo he hecho grupos de 2 referencias aquí, pero la posibilidad de agrupar 3 o más sería una ventaja.

¿Es posible implementar una versión sin cerradura de AtomicRefPair? Tengo la corazonada de que no es así, pero si no, tal vez haya un artículo en alguna parte que explique por qué.


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

+1

Hay un Interner en Guava, que usa ConcurrentHashMap, por lo que la contención podría ser arbitrariamente pequeña en promedio. – maaartinus

Respuesta

3

No sé si hay una buena solución, pero el siguiente fea podría funcionar:

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; 
} 
  • Uso MiReferencia en lugar de AtomicReference.
  • Utiliza una gran cantidad de bloqueos, pero ninguno de ellos es global.
  • Adquiere cerraduras en un orden fijo, por lo que está libre de interbloqueo.
  • Compila usando lombok y guava (tómelo como pseudocódigo sin ellos).
+0

+1. Casi lo que quería sugerir. Sin embargo, op necesita "cientos de miles de estas referencias". Quizás sería mejor agruparlos en particiones y asociar bloqueos con estas particiones, casi de la misma manera que funciona 'ConcurrentHashMap'. Si el número de particiones es razonablemente grande, la probabilidad de contención debe ser baja. – axtavt

+0

No lo haría, ya que ReentrantLock es increíblemente pequeño ya que contiene solo una referencia a un Objeto vacío. Así que incluso un millón de ellos cuesta solo 24 MB (suponiendo 8 B por referencia y 16 B por objeto). – maaartinus

+0

@maaartinus, ¿dónde está la referencia a lombok? – finnw

4

Tenga una clase inmutable sosteniendo el par. Ese es tu átomo. Intercambiar el par significa reemplazar el átomo.

actualización: su pregunta no es muy clara. pero en general, para un sistema simultáneo que consta de múltiples variables, uno puede querer

  1. tomar una instantánea del estado del sistema. la instantánea no cambia una vez tomada.
  2. atómicamente actualizan el estado del sistema cambiando varias variables a la vez. puede ser necesario que no haya otra actualización entre mi actualización y una instantánea anterior (en la que se basaba mi cálculo)

Puede modelar su sistema directamente en instantáneas, si no consume demasiados recursos.

+0

Consulte el primer punto de la pregunta. – finnw

+0

@finnw - ¿Qué pasa con esta respuesta? esta es una solución razonable (y mucho menos complicada). – jtahlborn

+0

Me refería al hecho de que 'r1' y' r2' pueden emparejarse para una operación y 'r1' y' r3' pueden emparejarse para otra. Esto podría funcionar en principio, pero necesitaría una forma de volver a agrupar dinámicamente las referencias. – finnw

Cuestiones relacionadas