我正在编写一个类,当调用它时会调用一个方法来使用系统时间来生成一个唯一的8个字符的字母数字作为引用ID。但我担心在某些时候,可能会在同一毫秒内进行多次调用,从而产生相同的参考ID。如何从可能同时调用此方法的多个线程中保护此调用到系统时间?
答案 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不会给我带来任何重复。
您最好的选择可能仍然是使用AtomicInteger
或AtomicLong
等同步计数器,并使用上述代码对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;
}
}
此代码是线程安全的,可以同时访问。