2014-06-13 10 views
5

Ich verwendete AtomicReference, um AtomicInteger zu implementieren. Aber während des Testens merke ich sogar in einer Single-Threaded-Umgebung, dass die CAS-Operation stecken geblieben ist, sobald ihr Wert 128 erreicht hat. Mache ich etwas falsch oder gibt es einen Vorbehalt in AtomicReference (kann mit der CPU zusammenhängen)? Hier ist mein Code:Warum AtomicReference CAS false mit Wert 128 zurückgeben?

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

Warum versuchen Sie, Ihren eigenen 'AtomicInteger' zu implementieren, wenn das JDK dies bietet? Befindest du dich auf einer Plattform, auf der es nicht unterstützt wird, oder ist das eine Übung? – Axel

+0

Dies ist nur eine Übung, um sich mit dem java.util.concurrent.atomic-Paket vertraut zu machen, und ja, wir müssen definitiv die Räder nicht neu erfinden :) – Alan

Antwort

9

Der Grund dafür ist, dass, wenn Sie einen int in eine Integer Box, können Sie oder keine neue Integer Instanz erstellen können. Wenn Sie dies tun, weist die neue Instanz möglicherweise keine Referenzgleichheit mit anderen Instanzen auf, auch wenn sie denselben Wert haben. AtomicReference.compareAndSet() verwendet Referenzgleichheit (Identität) für Vergleiche.

Der Schlüssel ist, wie der Compiler das automatische Boxen von int Werte behandelt: es sendet Aufrufe an Integer.valueOf(). Als Optimierung hat Integer.valueOf() einen Cache von Boxed Integers, und standardmäßig enthält dieser Cache Werte bis zu 128. Wenn Sie eine Integer n zweimal boxen, erhalten Sie immer die gleiche Integer Referenz zurück, wenn der Wert klein genug war der Cache; Andernfalls erhalten Sie zwei separate Instanzen.

Derzeit entpacken Sie den alten Wert, berechnen Sie den neuen Wert, und wenn Sie compareAndSet() aufrufen, boxen Sie den alten Wert erneut. Sobald Sie 128 erreicht haben, hören Sie auf, zwischengespeicherte Werte zu erhalten, sodass die zweite Kopie nicht länger die gleiche ist wie die in der AtomicReference.

+0

Danke, das hat mein Problem gelöst! Ich war mir nicht bewusst, dass 'AtomicReference.compareAndSet()' auf Referenzvergleich basiert. Nachdem ich das 'int oldVal = ref.get();' mit 'Integer oldVal = ref.get();' ersetzt habe, um Auto-Boxen zu vermeiden, funktioniert der Code gut. – Alan

+0

Großartig! Beachten Sie dies, wenn Sie 'AtomicInteger.compareAndSet()' mit einer 'AtomicReference' implementieren müssen :). –

+0

Und +1 für Ihre Erklärung des 'Integer.valueOf()' Cache-Mechanismus. – Alan

Verwandte Themen