2017-10-28 47 views
0

我現在閱讀在Java中思考關於原子性和可見性的章節。有一個我不明白的例子。Java可見性和同步 - Java示例中的思考

public class SerialNumberGenerator { 
    private static volatile int serialNumber = 0; 

    public static int nextSerialNumber() { 
     return serialNumber++; 
    } 
} 

class CircularSet { 
    private int[] array; 
    private int len; 
    private int index = 0; 

public CircularSet(int size) { 
    array = new int[size]; 
    len = size; 
    for (int i = 0; i < size; i++) { 
     array[i] = -1; 
    } 
} 

synchronized void add(int i) { 
    array[index] = i; 
    index = ++index % len; 
} 

synchronized boolean contains(int val) { 
    for (int i = 0; i < len; i++) { 
     if (array[i] == val) 
      return true; 
    } 
    return false; 
} 

}

公共類SerialNumberChecker {

private static final int SIZE = 10; 
private static CircularSet serials = new CircularSet(1000); 
private static ExecutorService exec = Executors.newCachedThreadPool(); 

static class SerialChecker implements Runnable { 

    @Override 
    public void run() { 
     while (true) { 
      int serial = SerialNumberGenerator.nextSerialNumber(); 
      if (serials.contains(serial)) { 
       System.out.println("Duplicate: " + serial); 
       System.exit(0); 
      } 
      serials.add(serial); 
     } 
    } 
} 

public static void main(String[] args) throws Exception { 
    for (int i = 0; i < SIZE; i++) { 
     exec.execute(new SerialChecker()); 
    } 
} 

}

輸出示例:

Duplicate: 228 

我不明白這怎麼可能。即使方法nextSerialNumber()也不同步,並且所有線程都會生成不同的值,每個線程都有自己的序列值,每個值都不相同。那麼怎麼可能找到重複的。我無法想象線程執行。

回答

1

此示例顯示增量後運算符不是原子的,也不是線程安全的。

在該代碼發生的是:

  • 許多(高達100)線程開始,各執行相同的代碼
  • 在無限循環:
    • 非同步的方法nextSerialNumber是調用後返回運算符的結果調用靜態變量
    • 調用了一個同步方法包含的內容,該函數檢查返回的值是否存在於基礎集合中
    • 如果是,該程序被終止
    • 如果不是,該值被添加到底層集合

如果增加後的操作是線程安全的,則程序將打印,也不要「重複「並且永不終止,因爲每個線程都會得到不同的序列號值。情況並非如此,因爲兩個線程 可能獲得完全相同的序列號值。