2011-09-27 10 views

risposta

6

Se siete preoccupati che contesa utilizzando CAS o synchronized allora si potrebbe prendere in considerazione qualcosa di più sofisticato, come la proposta di JSR 166E LongAdder (source, javadoc).

Questo è un contatore semplice con bassa contesa su accesso multithread. Si può avvolgere per esporre (valore attuale valore massimo mod). Cioè, non memorizzare affatto il valore avvolto.

1

È possibile utilizzare la classe java.util.concurrent.atomic.AtomicInteger per incrementare atomicamente. Per quanto riguarda l'impostazione di un limite superiore e il rollback su 0, è necessario farlo esternamente ... forse incapsulando tutto ciò all'interno della propria classe wrapper.

In realtà, è possibile utilizzare compareAndSet per controllare il limite superiore e quindi passare a 0.

18

E 'facile da implementare tale contatore cima AtomicInteger:

public class CyclicCounter { 

    private final int maxVal; 
    private final AtomicInteger ai = new AtomicInteger(0); 

    public CyclicCounter(int maxVal) { 
     this.maxVal = maxVal; 
    } 

    public int cyclicallyIncrementAndGet() { 
     int curVal, newVal; 
     do { 
      curVal = this.ai.get(); 
      newVal = (curVal + 1) % this.maxVal; 
     } while (!this.ai.compareAndSet(curVal, newVal)); 
     return newVal; 
    } 

} 
+0

Mi chiedo se questo sarebbe veramente atomica se filo perde la priorità tra '' 'this.ai.get()' '' e '' '} while (! This.ai.compareAndSet (CURVAL, newal)) '' ' –

+0

@ Renato Indietro: Sì, lo farebbe. CAS semantic lo garantisce. –

2

Personalmente ritengo la soluzione AtomicInteger è un po' brutto, perché introduce una gara condizionata che significa che il tentativo di aggiornamento potrebbe "fallire" e hanno da ripetere (eseguendo un'iterazione all'interno del ciclo while) rendendo il tempo di aggiornamento meno deterministico rispetto all'effettuazione dell'intera operazione in una sezione critica.

Scrivere il proprio contatore è così banale che consiglierei questo approccio. È anche più bello da una prospettiva OO in quanto espone solo le operazioni che ti è consentito eseguire.

public class Counter { 
    private final int max; 
    private int count; 

    public Counter(int max) { 
    if (max < 1) { throw new IllegalArgumentException(); } 

    this.max = max; 
    } 

    public synchronized int getCount() { 
    return count; 
    } 

    public synchronized int increment() { 
    count = (count + 1) % max; 
    return count; 
    } 
} 

EDIT

L'altro problema percepisco con la soluzione ciclo while è che, dato un gran numero di thread che tentano di aggiornare il contatore si potrebbe finire con una situazione in cui si dispone di diverse discussioni dal vivo girando e tentando di aggiornare il contatore. Dato che solo 1 thread avrebbe avuto successo, tutti gli altri thread avrebbero fallito causandone l'iterazione e sprecando i cicli della CPU.

+0

Dovresti anche implementare toString() se pensi di voler stampare un contatore. – GreenMatt

+0

Per quanto riguarda le prestazioni, il numero intero atomico sembra essere più veloce. Vedi https://gist.github.com/1245597 – sheki

+0

Adamski, il ciclo while è quello di fornire i vantaggi che normalmente otteniamo dalla sincronizzazione. La tua soluzione avrà lo stesso tempo di aggiornamento non deterministico (dal punto di vista del chiamante). – CPerkins

2

Se si utilizza l'operatore modulo, è possibile solo incrementare e restituire il modulo. Sfortunatamente l'operatore del modulo è costoso, quindi incoraggio altre soluzioni in cui le prestazioni sono importanti.

public class Count { 
    private final AtomicLong counter = new AtomicLong(); 
    private static final long MAX_VALUE = 500; 
    public long getCount() { 
     return counter.get() % MAX_VALUE; 
    } 
    public long incrementAndGet(){ 
     return counter.incrementAndGet() % MAX_VALUE; 

    } 
} 

Si dovrebbe risolvere anche il caso Long.MAX_VALUE.

5

con Java 8

public class CyclicCounter { 

    private final int maxVal; 
    private final AtomicInteger counter = new AtomicInteger(0); 

    public CyclicCounter(int maxVal) { 
     this.maxVal = maxVal; 
    } 

    return counter.accumulateAndGet(1, (index, inc) -> { 
     return ++index >= maxVal ? 0 : index; 
    });  

}

1

devo creare un ticker circolare simile per una logica personalizzata Akka Routing che doveva essere diverse da quelle di default per evitare l'overhead di rete, dal momento che la mia logica è solo per scegliere il prossimo percorso.

Nota: copiato dal Java 8 attuazione suggerito:

import akka.routing.Routee; 
import akka.routing.RoutingLogic; 
import scala.collection.immutable.IndexedSeq; 

import java.util.concurrent.atomic.AtomicInteger; 

public class CircularRoutingLogic implements RoutingLogic { 

    final AtomicInteger cycler = new AtomicInteger(); 

    @Override 
    public Routee select(Object message, IndexedSeq<Routee> routees) { 
    final int size = routees.size(); 
    return size == 0 ? null : routees.apply(cycler.getAndUpdate(index -> ++index < size ? index : 0)); 
    } 
} 
0

Per contro circolare altamente intensive incrementato di più thread in parallelo, consiglio usando LongAdder (dal java 8, vedere l'idea centrale all'interno di Striped64.java), perché è più scalabile rispetto a AtomicLong. È facile adattarlo alle soluzioni di cui sopra.

Si presume che l'operazione get non sia così frequente in LongAdder. Quando chiami counter.get, applica a esso "counter.get% max_number". Sì, il modulo-operazione è costoso, ma non è frequente per questo caso d'uso, che dovrebbe ammortizzare il costo totale delle prestazioni.

Ricorda però che l'operazione get non è bloccante, né atomica.

+1

LongAdder ha il metodo? Intendi somma()? sum(), yes, non è atomico, quindi non rappresenterebbe un problema con il rollover sul limite superiore su 0? Ad ogni modo, è interessante vedere l'implementazione * effettiva * ... –