很多業(yè)務(wù)系統(tǒng)中有類似的需求:生成一個唯一的ID舶胀,作為訂單記錄概说,一般ID中存在一個時(shí)間戳。為了防止多線程生成重復(fù)嚣伐,我們會在方法上加鎖糖赔,同時(shí),對于集群的情況轩端,會給每臺機(jī)器編號放典,這樣一般能保證唯一性。根據(jù)這個思路基茵,我們一般會寫如下的代碼來實(shí)現(xiàn):
public class UIDUtil {
private static int seqInt = 0;
private static final int ROTATION = 99999;
private static final String TIME_PATTERN = "yyyyMMddHHMMss";
private static ReentrantLock lock = new ReentrantLock();
/**
* 由于ROTATION的限制奋构,每秒最多生成99999個不同的uid,否則會重復(fù)
* @return
*/
public static String nextLock(){
lock.lock();
try{
if(seqInt > ROTATION){
seqInt = 0;
}
return String.format("%s%04d", DateTimeUtil.format(LocalDateTime.now(), TIME_PATTERN), seqInt++);
}finally {
lock.unlock();
}
}
}
代碼中的實(shí)現(xiàn)比較中規(guī)中矩,取一個時(shí)間戳拱层,加個數(shù)字做rotation弥臼,方法是加鎖的,保證在多線程競爭的情況下根灯,同一秒不會有同樣的序列號径缅。
但是掺栅,我們知道,多線程之間的鎖進(jìn)程會引起上下文切換纳猪,嘗試避免鎖可以提高性能氧卧,比如CAS,此處氏堤,我們就可以使用Atomic包的CAS方法來實(shí)現(xiàn):
public class UIDUtil {
private static AtomicInteger seq = new AtomicInteger(0);
private static final int ROTATION = 99999;
private static final String TIME_PATTERN = "yyyyMMddHHMMss";
/**
* 由于ROTATION的限制沙绝,每秒最多生成99999個不同的uid,否則會重復(fù)
* @return
*/
public static String next() {
for (; ; ) {
if (seq.intValue() < ROTATION || seq.compareAndSet(ROTATION, 0)) {
break;
}
}
for (; ; ) {
int current = seq.intValue();
if (current < ROTATION && seq.compareAndSet(current, ++current)) {
return String.format("%s%04d", DateTimeUtil.format(LocalDateTime.now(), TIME_PATTERN), current);
}
}
}
}
代碼中有兩個自旋的過程,第一個過程用于保證seq的值小于ROTATION的值鼠锈,第二個過程二次保證當(dāng)前獲取到的值小于ROTATION宿饱,這也會防止第一步陷入死循環(huán),同時(shí)對seq進(jìn)行自增脚祟。
筆者對兩個方法進(jìn)行了測試谬以,使用自己寫代碼的機(jī)器,采用10個線程由桌,每個線程生成100W個ID为黎,第二個方法耗時(shí)是第一個的1/3左右。