12

I am planning to write a sequence generator which will be used in my REST resource implementation class during post to generate unique id. Since every post request is handled by separate thread, I made the variable volatile and method synchronized. I don't have option to use sequences or something which traditional RDBMS provides.

public class SequenceGen {
    volatile static int n = 0;  
    public synchronized int nextNum(){
        return n++;
    }   
}

this is what I have so far, and planning to create a variable of SequenceGen in my REST Implementation. My actual question is will it break somewhere ? I tested with two threads and I dont see any value repeated.

Lucky
  • 16,787
  • 19
  • 117
  • 151
Ayan
  • 515
  • 4
  • 9
  • 20
  • 1
    make `nextNum` method `static` to make it sure. – Braj Mar 14 '14 at 22:25
  • If your initial tests show it works and your logic says it should work, generally you shouldn't worry about it anymore and accept that it works (for now). Worry about it when it actually poses a problem. – Jeroen Vannevel Mar 14 '14 at 22:25
  • 7
    Why not just use an `AtomicInteger`? – fge Mar 14 '14 at 22:25
  • More than one jvm involved in your process? In your POC, I suppose you are using a single jvm. – ram Mar 14 '14 at 22:26
  • The field should be private. volatile is redundant, since you access it using a synchronized method. But I agree with fge: an AtomicInteger is a better, safer, faster solution. If you plan to have several VMs, you should consider using a UUID instead (but you'll get a String, not an int) – JB Nizet Mar 14 '14 at 22:29
  • @gsndev, I am using single JVM only – Ayan Mar 14 '14 at 22:30
  • @JB Nizet, Thanks for pointing private and redundancies. . Initially I just wanted to keep only volatile. I will AtomicInteger and consider using it. – Ayan Mar 14 '14 at 22:36
  • volatile isn't sufficient. `++` is not an atomic operation, and you could thus have a race condition. ALso, your methos should definitely be static. Otherwise, your threads will synchronize on two different objects, which means you won't have synchronization. That's a bug you wouldn't have with AtomicInteger, which is inherently thread-safe. – JB Nizet Mar 14 '14 at 22:37
  • @JB Nizet, Reads and writes are atomic for all variables declared volatile (including long and double variables). from http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html – Ayan Mar 14 '14 at 22:48
  • @Ayan: A read is atomic. A write is atomic. But `++` makes a read, then an incrementation, then a write. And that sequence of operations is not atomic. So two threads might read the same value, increment it in parallel, and write the same next value in parallel. That's why `AtomicInteger.incrementAndGet()` exists. – JB Nizet Mar 14 '14 at 22:51
  • @JB Nizet, thanks for explanation. I will use AtomicInteger – Ayan Mar 14 '14 at 22:53
  • This static value will be lost when we restart the server right? – Shafs Jan Aug 16 '17 at 18:42

3 Answers3

27

It will work, however AtomicInteger is an built in type that is perfect for your use case.

AtomicInteger seq = new AtomicInteger();
int nextVal = seq.incrementAndGet();
Zhedar
  • 3,480
  • 1
  • 21
  • 44
anttix
  • 7,709
  • 1
  • 24
  • 25
  • 1
    +1 There's also a good existing post on using AtomicInteger an a sequence: http://stackoverflow.com/questions/14338533/atomicinteger-and-volatile – paulk23 Mar 14 '14 at 22:34
1

If you are open to using String for IDs, instead of int, you might want to look into using UUID (Universally Unique Identifier). Very easy to use and as the name implies, they are unique. Here is an example of how to generate one:

// the value of uuid will be something like '03c9a439-fba6-41e1-a18a-4c542c12e6a8'
String uuid = java.util.UUID.randomUUID().toString()

A UUID also provides better security than int because with integers you can guess the next request ID by simply adding 1 to your request ID, but UUIDs are not sequential and chances of anyone guessing anyone else's request ID are pretty slim.

user6376
  • 3
  • 1
  • 4
SergeyB
  • 9,478
  • 4
  • 33
  • 47
  • 1
    Although your comments are correct, there is a huge performance hit when using UUID – Ioannis Deligiannis Oct 08 '14 at 11:06
  • @IoannisDeligiannis - please elaborate. How much of a performance hit is it? – SergeyB Mar 01 '16 at 21:32
  • In my case, performance was very bad so I attached a profiler and realised that it actually hits the HDD. You can always add it in a loop and see how slow it is for you HW. In the below answer it was 1100ms. http://stackoverflow.com/questions/14532976/performance-of-random-uuid-generation-with-java-7-or-java-6 – Ioannis Deligiannis Mar 01 '16 at 22:01
1

You can take advantage of java.util.prefs.Preferences to persist the current state of your sequence generator on the disk and use it again later.

(also, you may want to use several sequence generators)

i.e.

import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.prefs.Preferences;

public final class SequenceGenerator {

    private static final Preferences PREFS = Preferences.userNodeForPackage(SequenceGenerator.class);
    private static final AtomicLong SEQ_ID = new AtomicLong(Integer.parseInt(PREFS.get("seq_id", "1")));
    private static final Map<Long, SoftReference<SequenceGenerator>> GENERATORS = new ConcurrentHashMap<>();
    private static final SequenceGenerator DEF_GENERATOR = new SequenceGenerator(0L, Long.parseLong(PREFS.get("seq_0", "1")));

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            GENERATORS.values().stream()
                    .map(SoftReference::get)
                    .filter(seq -> seq != null && seq.isPersistOnExit())
                    .forEach(SequenceGenerator::persist);
            if (DEF_GENERATOR.isPersistOnExit()) {
                DEF_GENERATOR.persist();
            }
            PREFS.put("seq_id", SEQ_ID.toString());
        }));
    }

    private final long sequenceId;
    private final AtomicLong counter;
    private final AtomicBoolean persistOnExit = new AtomicBoolean();

    private SequenceGenerator(long sequenceId, long initialValue) {
        this.sequenceId = sequenceId;
        counter = new AtomicLong(initialValue);
    }

    public long nextId() {
        return counter.getAndIncrement();
    }

    public long currentId() {
        return counter.get();
    }

    public long getSequenceId() {
        return sequenceId;
    }

    public boolean isPersistOnExit() {
        return persistOnExit.get();
    }

    public void setPersistOnExit(boolean persistOnExit) {
        this.persistOnExit.set(persistOnExit);
    }

    public void persist() {
        PREFS.put("seq_" + sequenceId, counter.toString());
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        GENERATORS.remove(sequenceId);
        if (persistOnExit.get()) {
            persist();
        }
    }

    @Override
    public int hashCode() {
        return Long.hashCode(sequenceId);
    }

    @Override
    public boolean equals(Object obj) {
        return obj == this || obj != null && obj instanceof SequenceGenerator && sequenceId == ((SequenceGenerator) obj).sequenceId;
    }

    @Override
    public String toString() {
        return "{" +
                "counter=" + counter +
                ", seq=" + sequenceId +
                '}';
    }

    public static SequenceGenerator getDefault() {
        return DEF_GENERATOR;
    }

    public static SequenceGenerator get(long sequenceId) {
        if (sequenceId < 0) {
            throw new IllegalArgumentException("(sequenceId = " + sequenceId + ") < 0");
        }
        if (sequenceId == 0) {
            return DEF_GENERATOR;
        }
        SoftReference<SequenceGenerator> r = GENERATORS.computeIfAbsent(sequenceId, sid -> {
            try {
                return new SoftReference<>(new SequenceGenerator(sid, Long.parseLong(PREFS.get("seq_" + sid, null))));
            } catch (Throwable t) {
                return null;
            }
        });
        return r == null ? null : r.get();
    }

    public static SequenceGenerator create() {
        return create(1);
    }

    public static SequenceGenerator create(long initialValue) {
        long sequenceId = SEQ_ID.getAndIncrement();
        SequenceGenerator seq = new SequenceGenerator(sequenceId, Long.parseLong(PREFS.get("seq_" + sequenceId, "" + initialValue)));
        GENERATORS.put(sequenceId, new SoftReference<>(seq));
        return seq;
    }

}
FaNaJ
  • 1,329
  • 1
  • 16
  • 39
  • Derive from this implementation, this version http://stackoverflow.com/questions/1186387/generating-9-digit-ids-without-database-sequence/42344842#42344842 supports cache and server crash recovery. – oraclesoon Feb 20 '17 at 13:40