I am writing a class that when called will call a method to use system time to generate a unique 8 character alphanumeric as a reference ID. But I have the fear that at some point, multiple calls might be made in the same millisecond, resulting in the same reference ID. How can I go about protecting this call to system time from multiple threads that might call this method simultaneously?
-
3Is there a reason you can't use something like [`UUID`](http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html)? Its generation is much safer. – Brian Sep 12 '12 at 16:37
-
I believe UUID uses system time as well, but I need it to be 8 characters long, max... – Aaron Sep 12 '12 at 16:38
-
It uses the system time, yes, but it also uses other stuff like `SecureRandom` (see [this question](http://stackoverflow.com/questions/2513573/how-good-is-javas-uuid-randomuuid)). You can ask the `UUID` for its bits (it has 128 of them, 2 `long` values, which is plenty for 8 characters) and use that to get the characters. – Brian Sep 12 '12 at 16:41
-
What would be a good method for converting the 128bits to an 8 character alphanum? I'm assuming this is just converting base 2 to base 36? – Aaron Sep 12 '12 at 17:07
-
That would be an excellent approach. I'll post an answer. – Brian Sep 12 '12 at 17:09
3 Answers
System time is unreliable source for Unique Ids. That's it. Don't use it. You need some form of a permanent source (UUID uses secure random which seed is provided by the OS)
The system time may go/jump backwards even a few milliseconds and screw your logic entirely. If you can tolerate 64 bits only you can either use High/Low generator which is a very good compromise or cook your own recipe: like 18bits of days since beginning of 2012 (you have over 700years to go) and then 46bits of randomness coming from SecureRandom - not the best case and technically it may fail but it doesn't require external persistence.
-
Honestly, if you're asking questions such as synchronizing system time, you shouldn't write your own. Use the tried and true `UUID` generation algorithms rather than something homebrewed :-) – Steven Schlansker Sep 12 '12 at 17:10
-
UUID is just secure random and 2 longs + setting the needed bits to match the UUID spec. But yeah, agree one'd not be asking questions on SO about sync time and attempting to do it on their own next. Yet, trying your own stuff, failing, trying anew and so is the way to learn. – bestsss Sep 12 '12 at 18:21
I'd suggest to add the threadID to the reference ID. This will make the reference more unique. However, even within a thread consecutive calls to a time source may deliver identical values. Even calls to the highest resolution source (QueryPerformanceCounter) may result in identical values on certain hardware. A possible solution to this problem is testing the collected time value against its predecessor and add an increment item to the "time-stamp". You may need more than 8 characters when this should be human readable. The most efficient source for a timestamp is the GetSystemTimeAsFileTime API. I wrote some details in this answer.
You can use the UUID
class to generate the bits for your ID, then use some bitwise operators and Long.toString
to convert it to base-36 (alpha-numeric).
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;
}
}
Since your character space is still smaller compared to UUID
, this isn't foolproof. Test it with this code:
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)");
}
For 100,000 IDs, the code performs well pretty consistently and is very fast. I start getting duplicate IDs when I increase another order of magnitude to 1,000,000. I modified the trimming to take the end of the encoded string instead of the beginning, and this greatly improved duplicate ID rates. Now having 1,000,000 IDs isn't producing any duplicates for me.
Your best bet may still be to use a synchronized counter like AtomicInteger
or AtomicLong
and encode the number from that in base-36 using the code above, especially if you plan on having lots of IDs.
Edit: Counter approach, in case you want it:
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;
}
}
This code is thread-safe and can be accessed concurrently.

- 17,079
- 6
- 43
- 66
-
I follow your code, and got it working, just not sure where you get "getId()" in your test... – Aaron Sep 12 '12 at 17:42
-
I just put `main` and `getId` (the method that creates the ID) into the same class. I suppose `getId` would have to be static in this context then, sorry. – Brian Sep 12 '12 at 17:44
-
The code performs much better if the trimming is done using `encoded = encoded.substring(encoded.length() - 8, encoded.length());` I'll update my answer. – Brian Sep 12 '12 at 17:49
-
I really like this approach with AtomicInteger. Lets say, though, that two different objects are created of this class... will AtomicInteger still be thread safe? (My understanding of mutexes is elementary at best) – Aaron Sep 12 '12 at 18:03
-
You should probably make the class a singleton or make the `AtomicInteger` static. Your choice, either will work. But AtomicInteger is guaranteed to be thread-safe. The definition of "atomic" here is that an operation (such as `getAndIncrement`) is guaranteed to happen in one go, without interference from other operations. Statements like `i++` (the `getAndIncrement` equivalent) aren't atomic in Java. If two threads call `i++` on the same variable at the same time, the result is undefined. Atomic variables fix this problem by doing both the increment and assignment before the next operation. – Brian Sep 12 '12 at 18:10
-
the backbone of UUID is just a SecureRandom, you do not need UUID per se. AtomicInteger/Long are useless unless persisted, if you need identity w/o persistence just use Object==Object and it's done. – bestsss Sep 12 '12 at 18:23
-
@bestsss Fair enough. `UUID` was more of a random thought and implementation. `SecureRandom.nextLong` would be a sufficient replacement for my lsb/msb manipulation, but running it through the testing I provided produces pretty much the same randomness/speed. In any case, the code is a little shorter and just as good. – Brian Sep 12 '12 at 18:39
-
Looks like I will need several instances of this, upwwards of several million, so the UUID is out. I will need to implement AtomicInteger. So if I understand you correctly, any time this "referenceID" class is called, I should invoke it as such: localID = referenceID.getInstance().getID(); That way the AtomicInteger is always always incremented, and never set back to 0? – Aaron Sep 13 '12 at 13:35
-
1@Aaron, AtomicInteger goes to negative (which you shall not care, though) after 2^31 (around 2 billions) instances and goes back to zero after the same amount more, so 4billions. – bestsss Sep 13 '12 at 14:12