2012-09-12 39 views
0

我正在編寫一個類,該類在調用時會調用一個方法來使用系統時間來生成唯一的8個字符的字母數字作爲參考ID。但是我擔心在某些時候,多個呼叫可能會在同一毫秒內完成,從而產生相同的參考ID。我怎麼能保護這個調用來自可能同時調用此方法的多個線程的系統時間?如何同步Java中的類中的系統時間訪問

+3

是否有一個原因,你不能使用['UUID'](http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html)?它的一代更安全。 – Brian

+0

我相信UUID也使用系統時間,但我需要它是8個字符長,最大... – Aaron

+0

它使用系統時間,是的,但它也使用其他的東西,如'SecureRandom'(見[這個問題](http://stackoverflow.com/questions/2513573/how-good-is-javas-uuid-randomuuid) )。你可以詢問'UUID'的位(它有128個,2個「長」值,這對於8個字符是足夠的),並用它來獲取字符。 – Brian

回答

4

系統時間是唯一ID的不可靠來源。而已。不要使用它。 您需要某種形式的永久性來源(UUID使用安全隨機,種子由操作系統提供)

系統時間可能會向後跳躍(甚至幾毫秒),並完全顛覆您的邏輯。如果你只能容忍64位,你可以使用High/Low generator這是一個非常好的折衷方案,或者自己製作食譜:比如自2012年初以來的18個天(你有超過700年的時間),然後是來自SecureRandom的46位隨機性 - 不是最好的情況和技術上它可能會失敗,但它不需要外部持久性。

+0

老實說,如果你提出的問題如同步系統時間,你不應該寫自己的。使用已經過驗證的真正的'UUID'生成算法,而不是自制的:-) –

+0

UUID只是安全的隨機數和2個longs +設置所需的位以符合UUID規範。但是,是的,同意不會就SO時間提出關於同步時間的問題,並嘗試單獨進行下一步。然而,嘗試自己的東西,失敗,重新嘗試,學習的方式也是如此。 – bestsss

0

我建議將threadID添加到參考ID。這將使參考更加獨特。但是,即使在一個線程中,對時間源的連續調用也可能會傳遞相同的值。即使調用最高分辨率源(QueryPerformanceCounter)也可能會在某些硬件上產生相同的值。解決這個問題的一個可能的方法是對上一次收集的時間值進行測試,並將增量項添加到「時間戳」中。這應該是人類可讀的,你可能需要超過8個字符。 時間戳最有效的來源是GetSystemTimeAsFileTime API。我在this的答案中寫了一些細節。

0

您可以使用UUID類爲您的ID生成位,然後使用一些按位運算符和Long.toString將其轉換爲基數36(字母數字)。

public static String getId() { 
    UUID uuid = UUID.randomUUID(); 
    // This is the time-based long, and is predictable 
    long msb = uuid.getMostSignificantBits(); 
    // This contains the variant bits, and is random 
    long lsb = uuid.getLeastSignificantBits(); 
    long result = msb^lsb; // XOR 
    String encoded = Long.toString(result, 36); 
    // Remove sign if negative 
    if (result < 0) 
     encoded = encoded.substring(1, encoded.length()); 
    // Trim extra digits or pad with zeroes 
    if (encoded.length() > 8) { 
     encoded = encoded.substring(encoded.length() - 8, encoded.length()); 
    } 
    while (encoded.length() < 8) { 
     encoded = "0" + encoded; 
    } 
} 

由於相比UUID你的性格空間仍然較小,這並非萬無一失。使用以下代碼進行測試:

public static void main(String[] args) { 
    Set<String> ids = new HashSet<String>(); 
    int count = 0; 
    for (int i = 0; i < 100000; i++) { 
     if (!ids.add(getId())) { 
      count++; 
     } 
    } 
    System.out.println(count + " duplicate(s)"); 
} 

對於100,000個ID,代碼執行得非常穩定,速度非常快。 當我將另一個數量級增加到1,000,000時,我開始獲取重複ID。 我修改了修剪以取代編碼字符串的末尾而不是開頭,這大大提高了重複ID率。現在有1,000,000個ID不會爲我生成任何重複項。

您最好的選擇仍然是使用同步計數器,如AtomicIntegerAtomicLong,並使用上面的代碼對base-36中的數字進行編碼,特別是如果您打算擁有大量ID。


編輯:櫃檯的方式,如果你想它:

private final AtomicLong counter; 

public IdGenerator(int start) { 
    // start could also be initialized from a file or other 
    // external source that stores the most recently used ID 
    counter = new AtomicLong(start); 
} 

public String getId() { 
    long result = counter.getAndIncrement(); 
    String encoded = Long.toString(result, 36); 
    // Remove sign if negative 
    if (result < 0) 
     encoded = encoded.substring(1, encoded.length()); 
    // Trim extra digits or pad with zeroes 
    if (encoded.length() > 8) { 
     encoded = encoded.substring(0, 8); 
    } 
    while (encoded.length() < 8) { 
     encoded = "0" + encoded; 
    } 
} 

此代碼是線程安全的,可以同時訪問。

+0

我遵循你的代碼,並得到它的工作,只是不知道你在哪裏得到「getId()」在你的測試... – Aaron

+0

我只是把'main'和'getId'(創建ID的方法)類。我想'getId'在這種情況下必須是靜態的,那麼對不起。 – Brian

+0

如果使用'encoded = encoded.substring(encoded.length() - 8,encoded.length())完成修剪,代碼性能會好很多;'我會更新我的答案。 – Brian