2011-01-27 54 views

回答

148

有的AtomicInteger兩種主要用途:

  • 爲可以由許多線程使用同時

  • 作爲支持compare-and-swap指令原語的原子計數器(incrementAndGet()等)(compareAndSet() )來實現非阻塞算法。

    下面是從Brian Göetz's Java Concurrency In Practice非阻塞隨機數發生器的一個示例:

    public class AtomicPseudoRandom extends PseudoRandom { 
        private AtomicInteger seed; 
        AtomicPseudoRandom(int seed) { 
         this.seed = new AtomicInteger(seed); 
        } 
    
        public int nextInt(int n) { 
         while (true) { 
          int s = seed.get(); 
          int nextSeed = calculateNext(s); 
          if (seed.compareAndSet(s, nextSeed)) { 
           int remainder = s % n; 
           return remainder > 0 ? remainder : remainder + n; 
          } 
         } 
        } 
        ... 
    } 
    

    正如你可以看到,它基本上作品幾乎方式與incrementAndGet()相同,但執行的,而不是增量任意計算(calculateNext()) (並在返回之前處理結果)。

+1

我想我明白了第一次使用。這是爲了確保在再次訪問屬性之前計數器已經增加。正確?你能舉一個簡短的例子來介紹第二次使用嗎? – 2011-01-27 16:11:54

+4

您對第一次使用的理解是真實的 - 它只是確保如果另一個線程修改了「讀取」和「寫入該值+ 1」操作之間的計數器,則會檢測到該值,而不是覆蓋舊更新(避免「丟失更新「問題)。這實際上是`compareAndSet`的一個特例 - 如果舊值爲`2`,則該類實際調用`compareAndSet(2,3)` - 因此如果另一個線程同時修改了該值,則增量方法將有效地重新啓動從一開始就。 – 2011-01-27 16:22:20

+3

「餘數> 0→餘數:餘數+ n;」在這個表達式中是否有一個當n爲0時向n添加餘數的理由? – sandeepkunkunuru 2016-02-19 16:20:43

1

關鍵是它們允許安全地同時訪問和修改。它們通常用作多線程環境中的計數器 - 在引入它們之前,這必須是一個用戶編寫的類,它將同步塊中的各種方法封裝起來。

+0

我明白了。這是在屬性或實例充當應用程序內的全局變量的情況下。或者還有其他的情況可以想到嗎? – 2011-01-27 16:13:40

80

我能想到的絕對最簡單的例子是增加原子操作。

隨着標準整數:

private volatile int counter; 

public int getNextUniqueIndex() { 
    return counter++; // Not atomic, multiple threads could get the same result 
} 

隨着的AtomicInteger:

private AtomicInteger counter; 

public int getNextUniqueIndex() { 
    return counter.getAndIncrement(); 
} 

後者是執行簡單的突變的影響(特別是計數,或獨特的索引)非常簡單的方式,而不必訴諸同步所有訪問。

更復雜的自由同步邏輯可以通過使用compareAndSet()作爲一種類型的樂觀鎖的被採用 - 得到的電流值,在此基礎上計算結果,設置此結果IFF值仍然是用來做計算的輸入,否則重新開始 - 但計數示例非常有用,如果涉及多個線程的任何提示,我經常使用AtomicIntegers進行計數和VM範圍的唯一生成器,因爲它們很容易與I'd幾乎認爲使用普通的ints是不成熟的優化。

雖然幾乎總是可以實現與ints和適當​​聲明相同的同步保障,中AtomicInteger的美妙之處在於線程安全性內置到實際的對象本身,而不是你需要擔心的可能的交錯,並監視每個發生訪問int值的方法。在調用getAndIncrement()時,意外地違反線程安全性要比返回i++並記住(或不)要事先獲取正確的監視器集合更困難。

+2

感謝您的明確解釋。使用AtomicInteger比方法全部同步的類有什麼優勢?後者會被認爲是「更重」嗎? – 2011-01-27 16:14:58

13

例如,我有一個庫可以生成某些類的實例。這些實例中的每一個都必須具有唯一的整數ID,因爲這些實例表示要發送到服務器的命令,並且每個命令都必須具有唯一的ID。由於允許多個線程同時發送命令,因此我使用AtomicInteger來生成這些ID。另一種方法是使用某種類型的鎖和一個常規整數,但這種方法速度較慢,較不優雅。

29

AtomicInteger的主要用途是當您處於多線程環境中,並且您需要在不使用​​的情況下對整數執行線程安全操作。原始類型int上的分配和檢索已經是原子的,但AtomicInteger附帶了很多在int上不是原子的操作。

最簡單的是getAndXXXxXXAndGet。例如getAndIncrement()是一個等價於i++的原子,它不是原子的,因爲它實際上是三個操作的縮寫:檢索,添加和分配。 compareAndSet對於實現信號量,鎖定,鎖存器等非常有用。

使用AtomicInteger比使用同步執行相同操作更快,更具可讀性。

一個簡單的測試:

public synchronized int incrementNotAtomic() { 
    return notAtomic++; 
} 

public void performTestNotAtomic() { 
    final long start = System.currentTimeMillis(); 
    for (int i = 0 ; i < NUM ; i++) { 
     incrementNotAtomic(); 
    } 
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start)); 
} 

public void performTestAtomic() { 
    final long start = System.currentTimeMillis(); 
    for (int i = 0 ; i < NUM ; i++) { 
     atomic.getAndIncrement(); 
    } 
    System.out.println("Atomic: "+(System.currentTimeMillis() - start)); 
} 

我與Java 1.6的PC在3秒內的原子測試運行,而同步一個在約5.5秒運行一次。這裏的問題是同步操作(notAtomic++)非常短。因此,與操作相比,同步的成本非常重要。

除原子性外AtomicInteger可用作Integer的可變版本,例如Map s作爲值。

49

如果你看看AtomicInteger的方法,你會注意到它們傾向於對應於整數上的常見操作。例如:

static AtomicInteger i; 

// Later, in a thread 
int current = i.incrementAndGet(); 

是這種線程安全的版本:

static int i; 

// Later, in a thread 
int current = ++i; 

的方法映射是這樣的:
++ii.incrementAndGet()
i++i.getAndIncrement()
--ii.decrementAndGet()
i--i.getAndDecrement()
i = xi.set(x)
x = ix = i.get()

還有其它方便的方法,以及像compareAndSetaddAndGet

5

像gabuzo說,有時我使用的AtomicIntegers時,我想通過引用傳遞一個int。它是一個內置的類,具有特定於架構的代碼,因此比我可以快速編寫的任何MutableInteger更容易,更可能更優化。這就是說,這感覺就像是對班級的濫用。

3

您可以使用compareAndSwap(CAS)對原子整數或長整數實現非阻塞鎖。該"Tl2" Software Transactional Memory闡述這一點:

我們一個專用的版本寫鎖定每交易 存儲位置相關聯。在最簡單的形式中,版本化寫入鎖是一個 單字螺旋鎖,它使用CAS操作獲取鎖,並通過商店釋放它。由於只需要一個位來指示 鎖已被佔用,我們使用鎖字的其餘部分來保存 版本號。

它描述的是首先讀取的原子整數。將其分解爲忽略的鎖定位和版本號。試圖將CAS寫入鎖定位,並用當前版本號清除鎖定位集和下一個版本號。循環直到你成功,你就是擁有鎖的線程。通過設置鎖定位清零的當前版本號解鎖。本文描述了使用鎖中的版本號來協調線程在寫入時具有一致的一組讀取。

This article描述了處理器對比較和交換操作的硬件支持非常有效。它還聲稱:

無阻塞使用原子變量具有比低基於鎖的櫃檯更好 性能中度爭

4

我通常使用的AtomicInteger當我需要給IDS基於CAS的櫃檯到可以從多個線程接受或創建的對象,並且我通常將它用作我在對象的構造函數中訪問的類的靜態屬性。

5

在Java 8原子類已擴展了兩個有趣的功能:

  • INT getAndUpdate(IntUnaryOperator updateFunction)
  • INT updateAndGet(IntUnaryOperator updateFunction)

兩者都使用updateFunction到執行原子值的更新。區別在於第一個返回舊值,第二個返回新值。 updateFunction可以實現比標準更復雜的「比較和設置」操作。例如,它可以檢查原子計數器不低於零,通常它需要同步,這裏的代碼是無鎖:

public class Counter { 

     private final AtomicInteger number; 

     public Counter(int number) { 
     this.number = new AtomicInteger(number); 
     } 

     /** @return true if still can decrease */ 
     public boolean dec() { 
     // updateAndGet(fn) executed atomically: 
     return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0; 
     } 
    } 

的代碼是從Java Atomic Example拍攝。