UUID是一个不错的解决方案,但在后端使用方法是UUID.randomUUID()。
synchronized public void SecureRandom.nextBytes(byte[] bytes)
这样做很慢:每次id生成操作都需要锁定一个单一的监视器对象。
AtomicInteger更好,因为它在CAS操作中循环。但是,对于每个id生成操作,必须执行同步操作。
在下面的解决方案中,仅同步素数生成。同步是在易失性整数上进行的,因此速度快且线程安全。有了一组素数,可以在迭代中生成许多id。
固定数量的线程
编辑:固定线程数的解决方案
如果您事先知道将使用多少个线程来生成Id,则可以使用值生成ID
Id = I mod X + n*X
使用质数生成的ID
生成ID的方法是将质数作为因子相乘,其中X是线程数,I是线程号,N是一个本地变量,每次生成ID时会被递增。
这个解决方案的代码非常简单,但必须与整个程序架构集成。
我们在每个线程中使用不同的质数来生成不同的ID集合。
假设我们使用质数(2,3,5),那么生成的序列将是:
2, 2^2, 2^3, 2^4, 2^5,..., 2^64
当我们发现会产生溢出时,我们将因子滚动到下一个质数位置:
3, 2*3 , 2^2*3, 2^3*3, 2^4*3, 2^5*3,..., 2^62*3
并且接下来
3^2, 2*3^2 , 2^2*3^2, .....
生成类
编辑:必须使用AtomicInteger进行初次生成以确保正确性
每个IdFactorialGenerator类的实例将生成不同的id集合。
为了线程安全地生成Ids,只需使用ThreadLocal来设置每个线程的实例即可。仅在质数生成期间实现同步。
package eu.pmsoft.sam.idgenerator;
public class IdFactorialGenerator {
private static AtomicInteger nextPrimeNumber = 0;
private int usedSlots;
private int[] primes = new int[64];
private int[] factors = new int[64];
private long id;
public IdFactorialGenerator(){
usedSlots = 1;
primes[0] = Sieve$.MODULE$.primeNumber(nextPrimeNumber.getAndAdd(1));
factors[0] = 1;
id = 1;
}
public long nextId(){
for (int factorToUpdate = 0; factorToUpdate < 64; factorToUpdate++) {
if(factorToUpdate == usedSlots) {
factors[factorToUpdate] = 1;
primes[factorToUpdate] = Sieve$.MODULE$.primeNumber(nextPrimeNumber.getAndAdd(1));
usedSlots++;
}
int primeToExtend = primes[factorToUpdate];
if( primeToExtend < Long.MAX_VALUE / id) {
factors[factorToUpdate] = factors[factorToUpdate]*primeToExtend;
id = id*primeToExtend;
return id;
} else {
factors[factorToUpdate] = 1;
id = 1;
for (int i = 0; i < usedSlots; i++) {
id = id*factors[i];
}
}
}
throw new IllegalStateException("I can not generate more ids");
}
}
为了获得素数,我使用了在问题7中提供的Scala实现:
http://pavelfatin.com/scala-for-project-euler/。请注意保留HTML标签。
object Sieve {
def primeNumber(position: Int): Int = ps(position)
private lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i =>
ps.takeWhile(j => j * j <= i).forall(i % _ > 0))
}