如何在Java中同步类中的系统时间访问

时间:2012-09-12 16:36:11

标签: java time system synchronized

我正在编写一个类,当调用它时会调用一个方法来使用系统时间来生成一个唯一的8个字符的字母数字作为引用ID。但我担心在某些时候,可能会在同一毫秒内进行多次调用,从而产生相同的参考ID。如何从可能同时调用此方法的多个线程中保护此调用到系统时间?

3 个答案:

答案 0 :(得分:4)

系统时间是Unique ID的不可靠来源。而已。不要使用它。 您需要某种形式的永久源(UUID使用安全随机,操作系统提供种子)

系统时间可能会向后跳/甚至几毫秒,并完全搞砸你的逻辑。如果你只能容忍64位,你可以使用High/Low generator这是一个非常好的折衷方案或自己制作食谱:比如自2012年初以来的18位(你有700多年的时间),然后是46位的随机性来自SecureRandom - 不是最好的情况,从技术上来说它可能会失败,但它不需要外部持久性。

答案 1 :(得分:0)

我建议将threadID添加到参考ID。这将使参考更加独特。但是,即使在线程内,对时间源的连续调用也可以提供相同的值。即使调用最高分辨率源(QueryPerformanceCounter),也可能在某些硬件上产生相同的值。此问题的可能解决方案是针对其前任测试收集的时间值,并将增量项添加到“时间戳”。如果这应该是人类可读的,则可能需要超过8个字符。 时间戳的最有效源是GetSystemTimeAsFileTime API。我在this回答中写了一些细节。

答案 2 :(得分:0)

您可以使用UUID类为您的ID生成位,然后使用一些按位运算符和Long.toString将其转换为base-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;
    }
}

此代码是线程安全的,可以同时访问。