Last active
December 23, 2016 09:31
-
-
Save zhoulifu/0e75917a2db5bf1e1a710527d80c835a to your computer and use it in GitHub Desktop.
A high concurrency unique id generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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