2014-01-25 94 views
4

我閱讀的AtomicInteger和這些特性如何的操作都是原子,以及如何使之成爲多線程有用。的AtomicInteger增量不表現爲預期

我寫了下面的程序來測試一樣。

我期待設定的最終大小應該是1000,因爲每個線程循環500次,每次一個線程調用GETNEXT時間假設()它應該得到一個唯一的編號。

但產量總是低於1000我失去了什麼嗎?

public class Sequencer { 

private final AtomicInteger i = new AtomicInteger(0); 

public int getNext(){ 
    return i.incrementAndGet(); 
} 

public static void main(String[] args) { 

    final Sequencer seq = new Sequencer(); 

    final Set<Integer> set = new HashSet<Integer>(); 

    Thread t1 = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      for (int i=0; i<500; i++) 
       set.add(seq.getNext()); 

     } 
    },"T1"); 
    t1.start(); 


    Thread t2 = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      for (int i=0; i<500; i++) 
       set.add(seq.getNext()); 

     } 
    },"T2"); 

    t2.start(); 

    try { 
     t1.join(); 
     t2.join(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 

    System.out.println(set.size()); 

} 

}

回答

3

你缺少一個HashSet的是不是線程安全的。另外,集合的屬性會擦除所有重複的數字,所以如果AtomicInteger不是線程安全的,那麼測試會失敗。

嘗試使用ConcurrentLinkedQueue代替。

編輯:因爲它已經被問了兩次:使用同步集的作品,但它破壞了支持使用無鎖算法,如原子類的想法。如果在上面的代碼中用一個同步集合替換集合,那麼線程將不得不在每次調用add時被阻塞。

這將您的應用有效降低單線程的,因爲所做的唯一工作,偏偏同步。實際上,它甚至比單線程慢,因爲​​也是如此。所以,如果你想實際利用線程,不惜一切代價儘量避免​​。

+0

我想這一點 - '最後一組集= Collections.synchronizedSortedSet(新TreeSet的());'和它的工作。多謝你們。 –

+0

請參閱我的編輯爲什麼不使用同步。 – TwoThe

+0

我明白了你的意思。的使用上面設置的想法是試驗A)如果'AtomicInteger''incrementAndGet()'是原子,b)若操作是原子的,集將還給我大小()1000 if它們是唯一的。但我沒有意識到Hashset操作不是線程安全的並會導致此問題。當我切換到synchronizedSortedSet,我測試了兩者的AtomicInteger和原始int和發現與的AtomicInteger計數總是1000,並與原始的詮釋是不相符的。但我同意你的觀點,即如果你通過不必要的同步來進行線程安全操作,它就會勝過這個目的。 –

-1

試着用這種方式使用Collections同步。所以你面對problem.You可以使用Vector或任何集合類是線程安全的或運行兩個線程順序,如果你stricly需要使用HashSet的

public class Sequencer { 

    private final AtomicInteger i = new AtomicInteger(0); 

    public static void main(String[] args) { 

     final Sequencer seq = new Sequencer(); 

     final Set<Integer> notSafe = new HashSet<Integer>(); 
     final Set<Integer> set = Collections.synchronizedSet(notSafe); 
     Thread t1 = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       for (int i = 0; i < 500; i++) 
        set.add(seq.getNext()); 

      } 
     }, "T1"); 
     t1.start(); 


     Thread t2 = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       for (int i = 0; i < 500; i++) 
        set.add(seq.getNext()); 

      } 
     }, "T2"); 

     t2.start(); 

     try { 
      t1.join(); 
      t2.join(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 

     System.out.println(set.size()); 

    } 

    public int getNext() { 
     return i.incrementAndGet(); 
    } 
} 
+1

雖然同步組會的工作,它完全破壞了使用原子__lock-free__算法的原因。 – TwoThe

+0

爲什麼?我認爲這不會對那首先組只能容納唯一值的任何影響,所以如果你有沒有的AtomicInteger你暫時還沒有很好的同步,即使會有synchronizedSet,因爲有時會加1,並添加1如此設置將不允許您添加兩個相同的值。所以我認爲concurrentHashSet不會打破這個問題的概念。 – RMachnik

+0

我不知道你在說什麼,對不起。但是我在帖子中添加了一個編輯,也許有幫助。 – TwoThe

1

的HashSet不是線程安全的。

t1.start(); 
t1.join(); 

t2.start(); 
t2.join(); 
+0

如果我按順序運行兩個線程它將擊敗我的測試目的:) –

+0

kk然後使用線程安全類。 – Kick

0

正如在幾個答案中提到的,它由於HashSet不是線程安全的而失敗。

首先,讓我們驗證測試的緣故,的AtomicInteger確實是線程安全的,然後繼續看爲什麼你的測試失敗。稍微修改你的測試。使用兩個哈希集,每個線程一個哈希集。最後,在連接之後,將第二個集合合併爲第一個集合,迭代第二個集合並將其添加到第一個集合中,這將消除重複集合(set屬性)。然後指望第一套。計數將是你所期望的。這證明它是HashSet不是線程安全的,而不是AtomicInteger。

那麼讓我們來看看哪些方面不是線程安全的。你只做add()s,所以add()是不是線程安全的操作,導致數字的丟失。讓我們看一個僞代碼非線程安全的HashMap add(),它會丟失數字(這顯然不是它如何實現的,只是試圖說明它可能是非線程安全的一種方式)的示例:

class HashMap { 
int valueToAdd; 
public add(int valueToAdd) { 
    this.valueToAdd = valueToAdd; 
    addToBackingStore(this.valueToAdd); 
} 
} 

如果多個線程調用add(),並且它們在更改this.valueToAdd後都到達addToBackingStore(),則只會添加valueToAdd的最終值,其他所有值都會被覆蓋並丟失。

類似的東西到這可能是在您的測試發生了什麼。