4

I have a static method that is supposed to generate a unique ID based on the current timestamp as shown in the codes below. To ensure that the newly generated ID is not the same as the previously generated ID (due to very fast computer such that the millisecond does not change), I put in a loop to compare the newly generated ID against the previously generated one. If they are the same, it will generate another ID.

public class Util {

    protected static String uniqueID;

    public static String generateUniqueID() {
        SimpleDateFormat timstampFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        do {
            String timestamp = timstampFormat.format(new Date());
            if (!timestamp.equals(uniqueID)) {
                uniqueID = timestamp;
                return uniqueID;
            }
        } while (true);
    }

}

I want the above codes to work when the method is called by multiple threads.

If I merely put the volatile keyword to the uniqueID variable, would that be good enough? Do I still need to have a synchronized block?

How about having a synchronized block but without the volatile keyword?

Thanks in advance.

ADDED:

If I changed to below codes, would the volatile keyword still required?

public class Util {

    private static volatile String uniqueID;

    public static synchronized String generateUniqueID() {
        uniqueID = UUID.randomUUID().toString();
        return uniqueID;
    }

}
user3573403
  • 1,780
  • 5
  • 38
  • 64
  • are you familiar with what volatile keyword actually does? why do you think it would solve the issue? – eis Oct 31 '16 at 10:05
  • 4
    This is just terrible! What you done is taken a "very fast computer" and made it ridiculously slow. Not only do you create a `SimpleDateFormat` for every invocation (you could use a pool or, with Java 8, simply use [`DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) which is immutable and threadsafe) but you also busy wait. The **only** thing this code is good for a turning a powerful computer into an inefficient fan heater. Don't do this. Any of this. – Boris the Spider Oct 31 '16 at 10:07
  • 1
    I suggest you start reading some tutorials, e.g. [Oracle's Lesson on Concurrency](http://docs.oracle.com/javase/tutorial/essential/concurrency/). As of right now, you are just throwing around some terms without understanding their meaning. To your question: `volatile` would not be sufficient, I would synchronize the method. – Turing85 Oct 31 '16 at 10:07
  • 1
    @Turing85 as it currently stands it would be better to do away with threads altogether. The current code, with `synchronized`, does little else but make concurrent code synchronous. The lock contention would make a grown man cry... – Boris the Spider Oct 31 '16 at 10:09
  • 2
    Use UUID insteed of own code. – Antoniossss Oct 31 '16 at 10:16
  • See https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html#randomUUID() – David Lavender Oct 31 '16 at 10:18
  • 1
    It's important to realize that if you insist on your ID format, you won't be able to give more than one ID per millisecond, no matter what code you use, whether a busy wait or a sleep or a magic wand. There is only one ID for each millisecond, and only one caller will get it. – RealSkeptic Oct 31 '16 at 10:22
  • @RealSkeptic A situation where the saying "there aren't enough hours in the day" can literally be true. – biziclop Oct 31 '16 at 10:34
  • On your update - why do you need `uniqueID`? Delete that, and the `synchronized` keyword and you're good to go. – Boris the Spider Oct 31 '16 at 10:45
  • 1
    While the code is terrible, I think this is a good question to ask, presented quite clearly. Upvoted for that. – eis Oct 31 '16 at 12:27

4 Answers4

7

There are multiple issues with this code.

Never use a timestamp as UID, unless you're absolutely positive, there won't be ever generated multiple UIDs within a time that is smaller than the lowest resolution of the timestamp you're using. I'd recommend switching to a completely different approach. Either append a counter to the timestamp, if you absolutely want to keep the timestamp-format in use or simply use a counter. Another alternative would be to use System.nanoTime() in addition to the normal systemtime, though that approach might provide quite a few pitfalls.

Your while-loop will loop for up to an entire millisecond, if you try to generate two UIDs within the same millisecond. There's no fast computer needed to make this a total waste of CPU-time. The loop will at least run several thousand times without proper result.

Marking a variable volatile won't do. You have to mark the entire block that is run within the method synchronized to prevent multiple threads from running it at the same time. But consider a case, where you want to generate 1000 UIDs within a single ms. What should be done within no time now suddenly takes a full second. You're creating an enormous bottleneck.

My recommendation:
Delete that method immediately. There's not much that could fix this code to the point where it would actually be acceptable in terms of performance and correctness. Read this tutorial about concurrency. Get a new approach for generating UIDs and start over from scratch.

Alternatively:
Why even write code for something that already exists? Use the UID-class provided by Oracle. Another good approach would be to use UUID, which is part of the utility package and quite likely more general than UID. Depends on your demands on the generated UID.

2

You have a problem that you busy wait for each milli-second and if multiple threads are waiting, they will all wait, possibly endlessly. This will happen regardless of how you provide thread safety. A better approach is to use a counter which is always >= to the time or a counter which is many times the current time.

You can do

private static final AtomicLong time = new AtomicLong(0);
public static long uniqueTimedId() {
    while(true) {
         long now = System.currentTimeMillis();
         long value = time.get();
         long next = now > value ? now : value + 1;
         if (time.compareAndSwap(value, next))
            return next;
    }
}

This will give you an id for each milli-second, without waiting. When you have multiple ids in the same milli-second, some will be in the future. You can turn this into a String of your format if you want. This still has a problem that you can't have more than 1 per milli-second without drifting from the current time.

private static final AtomicLong time = new AtomicLong(0);
public static long uniqueTimedId() {
    while(true) {
         long now = System.currentTimeMillis() * 1000;
         long value = time.get();
         long next = now > value ? now : value + 1;
         if (time.compareAndSwap(value, next))
            return next;
    }
}

This gives you a unique id which at 1000x the current time. This means you can have 1000 ids per milli-second without drift, and when you convert to a String you need to divide by 1000 to get the time in millis and attach x % 1000 as three digits on the end.

A simpler compromise might be to multiply by 10. This gives you 10 ids per millisecond.

If an instance variable is modified/accessed through synchronized blocks, does that mean the latest modification can be seen by all threads.

Synchronized blocks use read/write memory barriers implemented by the CPU to ensure this happens.

Note: if you use synchronized you don't need to use volatile. In fact using both is could be slower.

If I changed to below codes, would the volatile keyword still required?

synchronized is needed as you are using a variable which is shared. The volatile is redundant and doesn't help.

private static String uniqueID;

public static synchronized String generateUniqueID() {
    uniqueID = UUID.randomUUID().toString();
    // without synchronized, one thread could read what another thread wrote
    return uniqueID;
}

If you use a local variable you wouldn't need synchronzied or volatile as nothing is shared.

// nothing is shared, so no thread safety issues.
public static String generateUniqueID() {
    return UUID.randomUUID().toString();
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1

Like others, I would heavily recommend getting rid of busy-loop, timestamp as identifier as well as synchronized-volatile thinking. Volatile will not work, synchronized would work but would be horribly inefficient.

What I would recommend however, is to

  • use compareAndSet of AtomicReference for your checking, as it is able to use actual atomic operations as supported by your CPU
  • use an identifier that is not bound on time, so you can avoid the bottleneck of waiting for time changes
  • if you insist on using timestamp as your identifier, at least sleep while you wait!
eis
  • 51,991
  • 13
  • 150
  • 199
1

Your field uniqueID must be volatile in order to make sure that all threads will see the latest modification and you should rely on UUID (stands for Universally unique identifier) to generate unique ids (that will even be unique in a cluster as it relies on the date time but also on the MAC address), your code will then be as next:

public static String generateUniqueID() {
    return UUID.randomUUID().toString();
}

public static UUID randomUUID()

Static factory to retrieve a type 4 (pseudo randomly generated) UUID. The UUID is generated using a cryptographically strong pseudo random number generator.

Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
  • Hi, see my edits above. So volatile is still required to ensure that all threads can see the latest modification? – user3573403 Oct 31 '16 at 10:41
  • @user3573403 yes it is still required – Nicolas Filotto Oct 31 '16 at 10:44
  • The example in https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html does not have volatile keyword on the instance variable. Does that mean that modification to the instance variable may not be seen immediately by all threads? – user3573403 Oct 31 '16 at 10:45
  • it is because the instance variable is only accessed/modified through synchronized blocks so in this case it is not needed, here I assume that you don't want to do that – Nicolas Filotto Oct 31 '16 at 10:47
  • I don't get what you mean. If an instance variable is modified/accessed through synchronized blocks, does that mean the latest modification can be seen by all threads. Why? – user3573403 Oct 31 '16 at 10:51
  • 1
    @user3573403 Because that's the rules. Concurrency isn't something you can learn effectively by trial and error (most things you can, but this one is different). If you feel unsure about why and where to put synchronized blocks, and how it relates to volatile fields, you really need to get yourself a book that explains how Java concurrency was designed to work. It is absolutely crucial that you understand the basic principles if you want to write concurrent code that is relatively error-free. – biziclop Oct 31 '16 at 10:55
  • yes read [this](http://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java) – Nicolas Filotto Oct 31 '16 at 10:56