Skip to content

Instantly share code, notes, and snippets.

@zhoulifu
Last active December 23, 2016 09:31
Show Gist options
  • Save zhoulifu/0e75917a2db5bf1e1a710527d80c835a to your computer and use it in GitHub Desktop.
Save zhoulifu/0e75917a2db5bf1e1a710527d80c835a to your computer and use it in GitHub Desktop.
A high concurrency unique id generator
/**
* Simple unique id generator at high concurrency. fork from
* <a href="https://github.com/twitter/snowflake/tree/snowflake-2010">snowflake</a>.
* <br/>
* The return value of {@link IdGenerator#nextId()} is consist of 41-bit timestamp and
* 10-bit worker id and 12-bit sequence, so it could support at most 1023 workers to
* generate 4095 ids per millisecond.
* <br/>
* Because the timestamp's bit width is 41, so it could be used for about 69.7 years
* {@code ((1<<41) / (365*24*3600*1000))}. The deadline depends on the
* {@link IdGenerator#EPOCH}.
*/
public class IdGenerator {
private static final long EPOCH = 1451577600000L; //2016-01-01 00:00:00
private static final int SEQUENCE_BITS = 12;
private static final int SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
private static final int WORKER_ID_BITS = 10;
private static final int MAX_WORK_ID = (1 << WORKER_ID_BITS) - 1;
private static final int TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private final long workerId;
private int sequence;
private long lastTimestamp = -1L;
public IdGenerator(int workerId) {
if (workerId < 0 || MAX_WORK_ID < workerId) {
throw new IllegalArgumentException(
"Worker id " + workerId + " is out of range [0-1023]");
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = currentTimeMillis();
if (this.lastTimestamp == timestamp) {
this.sequence = (this.sequence + 1) & SEQUENCE_MASK;
if (this.sequence == 0) {
// Wait until next millisecond if sequence runs out.
timestamp = waitTillNextMilli();
}
} else {
this.sequence = 0;
}
if (timestamp < this.lastTimestamp) {
long interval = this.lastTimestamp - timestamp;
throw new IllegalStateException(
"Refuse to generate id because clock moved back for " + interval +
" milliseconds");
}
this.lastTimestamp = timestamp;
return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
| (this.workerId << SEQUENCE_BITS)
| this.sequence;
}
private long waitTillNextMilli() {
long timestamp = currentTimeMillis();
while (timestamp <= this.lastTimestamp) {
timestamp = currentTimeMillis();
}
return timestamp;
}
private long currentTimeMillis() {
return System.currentTimeMillis();
}
}
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
public class IdGeneratorTest {
private static final int THREAD_COUNT = 8;
private static final int ID_COUNT_PER_THREAD = 500;
private static final int MAX_ID = 1023;
@Test
public void testIdUnique() throws InterruptedException {
int workerId = (int) (Math.random() * MAX_ID);
final IdGenerator gen = new IdGenerator(workerId);
final Set<Long> ids = new ConcurrentSkipListSet<Long>();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < ID_COUNT_PER_THREAD; i++) {
ids.add(gen.nextId());
}
}
};
ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
pool.submit(r);
}
pool.shutdown();
while (!pool.isTerminated()) {
pool.awaitTermination(500, TimeUnit.MILLISECONDS);
}
Assert.assertEquals(ids.size(), THREAD_COUNT * ID_COUNT_PER_THREAD);
}
@Test(expected = IllegalArgumentException.class)
public void testMaxId() {
int workerId = (int) (Math.random() * MAX_ID);
new IdGenerator(workerId); // it's ok
workerId = (int) (Math.random() * (Integer.MAX_VALUE - MAX_ID) + MAX_ID);
new IdGenerator(workerId); // id out of range
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment