2014-06-13 12 views
5

Stavo usando AtomicReference per implementare AtomicInteger. Tuttavia, durante i test, ho notato che anche in un singolo thread l'operazione CAS si è bloccata una volta che il suo valore ha raggiunto 128 .. Sto facendo qualcosa di sbagliato o c'è un avvertimento in AtomicReference (potrebbe essere correlato alla CPU)? Qui è il mio codice:Perché il CAS AtomicReference restituisce false con il valore 128?

public class MyAtomInt { 
    private final AtomicReference<Integer> ref; 

    public MyAtomInt(int init) { 
    ref = new AtomicReference<Integer>(init); 
    } 

    public MyAtomInt() { 
    this(0); 
    } 

    public void inc() { 
    while (true) { 
     int oldVal = ref.get(); 
     int nextVal = oldVal + 1; 
     boolean success = ref.compareAndSet(oldVal, nextVal); // false once oldVal = 128 
     if (success) { 
     return; 
     } 
    } 
    } 

    public int get() { 
    return ref.get(); 
    } 

    static class Task implements Runnable { 

    private final MyAtomInt myAtomInt; 
    private final int incCount; 

    public Task(MyAtomInt myAtomInt, int cnt) { 
     this.myAtomInt = myAtomInt; 
     this.incCount = cnt; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < incCount; ++i) { 
     myAtomInt.inc(); 
     } 
    } 
    } 

    public static void main(String[] args) throws Exception { 
    MyAtomInt myAtomInt = new MyAtomInt(); 
    ExecutorService exec = Executors.newSingleThreadExecutor(); 
    exec.submit(new Task(new MyAtomInt(), 150)).get(); 
    System.out.println(myAtomInt.get()); 
    exec.shutdown(); 
    } 
} 
+0

Perché stai cercando di implementare il tuo 'AtomicInteger' quando JDK lo fornisce? Sei su una piattaforma in cui non è supportato o è un esercizio? – Axel

+0

Questa è solo una pratica per familiarizzare con il pacchetto java.util.concurrent.atomic, e sì sicuramente non abbiamo bisogno di reinventare le ruote :) – Alan

risposta

9

La ragione di questo è che quando si Box un int in un Integer, si può o non può creare una nuova istanza Integer. In tal caso, la nuova istanza potrebbe non avere uguaglianza di riferimento con altre istanze Integer, anche se condividono lo stesso valore. AtomicReference.compareAndSet() utilizza uguaglianza di riferimento (identità) per il confronto.

La chiave è in come il compilatore gestisce il boxing automatico dei valori int: emette chiamate a Integer.valueOf(). Come ottimizzazione, Integer.valueOf() ha una cache di interi in scatola e, per impostazione predefinita, la cache include valori fino a 128. Se imposti un numero intero n due volte, riceverai lo stesso riferimento Integer ogni volta se il valore fosse abbastanza piccolo da essere in la cache; altrimenti, otterrai due istanze separate.

Al momento, si annulla il vecchio valore, si calcola il nuovo valore e quando si chiama compareAndSet() si inserisce nuovamente il vecchio valore . Una volta raggiunto il valore di 128, non si ottiene più i valori memorizzati nella cache, quindi la seconda copia in scatola non è più la stessa di AtomicReference.

+0

Grazie, questo ha risolto il mio problema! Non ero a conoscenza del fatto che 'AtomicReference.compareAndSet()' si basa sul confronto di riferimento. Dopo aver sostituito 'int oldVal = ref.get();' con 'Integer oldVal = ref.get();' per evitare il boxing automatico, il codice funziona correttamente. – Alan

+0

Ottimo! Tieni questo a mente quando devi implementare 'AtomicInteger.compareAndSet()' usando un 'AtomicReference' :). –

+0

E +1 per la spiegazione del meccanismo di cache 'Integer.valueOf()'. – Alan

Problemi correlati