1. java.security.SecureRandom源码分析
jdk产生uuid的代码:
public static UUID randomUUID() {
SecureRandom ng =Holder.numberGenerator;
byte[] randomBytes = newbyte[16];
ng.nextBytes(randomBytes);
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |=0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |=0x80; /* set to IETF variant */
return newUUID(randomBytes);
}
使用了SecureRandom.next*的方法。
在使用SecureRandom产生下一个随机数的时候调用nextLong或者nextBytes,最终会调用SecureRandom的nextBytes。而nextBytes是一个同步方法,会产生性能瓶颈。
public long nextLong() {
// it‘s okay that the bottom wordremains signed.
return ((long)(next(32)) << 32)+ next(32);
}
final protected int next(int numBits) {
int numBytes = (numBits+7)/8;
byte b[] = new byte[numBytes];
int next = 0;
nextBytes(b);
for (int i = 0; i < numBytes; i++)
next = (next << 8)+ (b[i] & 0xFF);
return next >>> (numBytes*8 -numBits);
}
而nextBytes是一个同步的方法
synchronized public void nextBytes(byte[] bytes) {
secureRandomSpi.engineNextBytes(bytes);
}
secureRandomSpi被初始化为sun.security.provider.SecureRandom
secureRandomSpi是SecureRandom.NativePRNG的一个实例。从securityprovider列表中可以看到,SecureRandom.NativePRNG由sun.security.provider.NativePRNG提供服务。
Provider: Set SUN provider property[SecureRandom.NativePRNG/sun.security.provider.NativePRNG]
分析openjdk的源码可以看到,NativePRNG.engineNextBytes调用了NativePRNG.RandomIO.ensureBufferValid,而ensureBufferValid直接从urandom读取数据:
private void ensureBufferValid() throws IOException {
...
readFully(urandomIn, urandomBuffer);
...
}
通过测试可以发现,hotspot需要使用配置项"-Djava.security.egd=file:/dev/./urandom"才能从urandom读取数据,这里openjdk做了优化,直接从urandom读取数据。
2. JUG源码分析
JUG的com.fasterxml.uuid.impl.TimeBasedGenerator.generate()可以产生基于时间的UUID。由于在同步块中,仅做了整数的运算,因此,同步影响较小。
原理是在初始化的时候,根据网卡、随机数、计算出了一部分固定的数据:
public TimeBasedGenerator(EthernetAddress ethAddr , UUIDTimertimer)
{
byte [] uuidBytes = new byte [16];
if (ethAddr == null) {
ethAddr = EthernetAddress.constructMulticastAddress();
}
// initialize baseline with MAC address info
_ethernetAddress = ethAddr ;
_ethernetAddress .toByteArray(uuidBytes , 10);
// and add clock sequence
int clockSeq = timer.getClockSequence();
uuidBytes [UUIDUtil. BYTE_OFFSET_CLOCK_SEQUENCE] = ( byte )(clockSeq >> 8);
uuidBytes [UUIDUtil. BYTE_OFFSET_CLOCK_SEQUENCE+1] = ( byte) clockSeq ;
long l2 = UUIDUtil.gatherLong (uuidBytes , 8);
_uuidL2 = UUIDUtil.initUUIDSecondLong( l2);
_timer = timer ;
}
在调用generate的时候,根据当前时间,加上一个counter,生成另一半随机数:
public UUID generate()
{
final long rawTimestamp = _timer.getTimestamp ();
// Time field components are kind of shuffled, need toslice:
int clockHi = ( int) ( rawTimestamp >>> 32);
int clockLo = ( int) rawTimestamp ;
// and dice
int midhi = ( clockHi << 16) | ( clockHi >>>16);
// need to squeeze in type (4 MSBs in byte 6, clock hi)
midhi &= ~0xF000; // remove high nibble of 6th byte
midhi |= 0x1000; // type 1
long midhiL = ( long) midhi ;
midhiL = ((midhiL << 32) >>> 32); // to getrid of sign extension
// and reconstruct
long l1 = ((( long) clockLo ) << 32) | midhiL ;
// last detail: must force 2 MSB to be ‘10‘
return new UUID( l1, _uuidL2);
}
public final synchronized long getTimestamp ()
{
long systime = System.currentTimeMillis ();
......
_clockCounter &= 0xFF;
systime *= kClockMultiplierL;
systime += kClockOffset; //由于uuid需要GregorianCalendar,这里将1970年的时间加到系统时间
// Plus add the clock counter:
systime += _clockCounter ;
// and then increase
++ _clockCounter ;
return systime ;
}