###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################
本文中关联的所有文章的总目录可以参看:系列文章目录
1 前言
为什么会讲这个类了?是因为我们在Netty源码分析一接口系列Channel接口 文章分析时,在分析id()这个方法时,涉及到了DefaultChannelId这个类,而这个类的算法其实就是一个雪花算法,针对这个算法其实网上有很多种,在这里我不再讲述;
2 DefaultChannelId 类中学习到的知识点
对于这个类的具体代码我还是在最后面放出来,供大家方便参看,在这里就不具体的讲解了,一般都能看的懂;
2.1 ThreadLocalRandom
这个类在获取随机数时,用到了下面这样的代码:
// random
int random = PlatformDependent.threadLocalRandom().nextInt();
而这个代码最终调用是下面这样的代码:
ThreadLocalRandom.current().nextInt();
ThreadLocalRandom 这个类是在jdk1.7版本才开始加入的,而这个随机数与Ramdom随机数有什么关联?在这里我们也不讲解了,网上有很多文章,大家搜索学习下;
我在这里大致说下我的理解,它们的区别:
Random
你如果共用一个Random实例,那么在并发的场景下就会产生竞争;这个竞争来源于获取nextXX()时,它们会在设置AtomicLong类型的seed时会有竞争。这是因为随机数在生成时,都会获取这个类的当前seed,然后按照一个固定的算法获取到一个新的seed,在这个过程中如果都是并发的计算,就导致了seed有资源竞争,其它线程进入CAS自旋重新获取随机数;
ThreadLocalRandom
而这个类如果是所有线程共用一个实例的话,那么在并发场景下是没有资源竞争的;所以在并发环境下它的效率很高;
而ThreadLocalRandom为什么能够做到效率高了?前面我已经说了,大家自行搜索了解。
而针对这个类使用,我们要特别注意一点:那就是每次使用时,我们都要通过ThreadLocalRandom.current()来获取对象调用其方法,而不是放到了个全局的静态变量中;
如果我们在全局中定义,就像下面这样的话:
public class RandomUtil{
public final static ThreadLocalRandom random = ThreadLocalRandom.current();
}
那么如果在并发环境中,我们都通过RandmUtil.random 来获取这个对象,然后生成随机数,我们会发现,不同的线程中,生成的随机数会重复;
这个原因我来具体说明下:
因为这个类初始化时进行了如下处理
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0){
// 当一个线程第一次调用这个方法时,它会进行一次初始化动作
localInit();
}
// 返回同一个该类中的实例,说明不管多少个线程调用这个方法,返回的都是同一个实例对象
return instance;
}
通过上面代码我们也可以看出,即使我产在不同的线程中调用ThreadLocalRandom.current();它返回的依然是同一个对象;
而对于localInit() 方法里面也主要是初始化这个类中的SEED,PROBE两个变量按线程维度进行赋值;也是一个线程放入一个值;
而对于取获取随机变量next(int)方法,我们看代码如下所示:
protected int next(int bits) {
return (int)(mix64(nextSeed()) >>> (64 - bits));
}
其中的变量就是nextSeed() 这个方法,其它的都是固定的算法公式;所以我们需要再看下nextSeed()方法的代码:
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
我们可以看到,它返回的是r这个变量值,而这个值是由UNSAFE.getLong(t, SEED) + GAMMA这个来获取的;
- t是Thread.currentThread(), 这个是根据每个线程都不一样的
- SEED 值这个是个固定的值(这个只是针对同一个虚拟机里面来说的),因为它是在在静态块中写的,代码如下所示:
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}
- GAMMA 这个也是固定的值
所以上面唯一可变的就是t了,但当并发情况下,因为我们是在公共的工具类中获取的ThreadLocalRandom这个变量,所以一开始所有的线程在执行UNSAFE.getLong(t, SEED)这个值时,返回的值不是自己线程设置的,所以这个值也一定是一个常量值,这样就导致了所有线程最终计算得到的r都相同,生成的随机数也相同;
那么为什么我们在使用时直接通过ThreadLocalRandom.current().next(XXX) 就可以保证随机了???虽然ThreadLocalRandom.current()在同一个虚拟机中返回的都是同一个实际对象。
那是因为在调用ThreadLocalRandom.current()时,它会根据当前线程第一次调用时调用localInit();这个方法,ThreadLocalRandom.current()这段代码我前面已经讲过了,localInit()方法的逻辑我们没有讲过,我们现在来看下:
static final void localInit() {
// 线程安全的获取一个变量,所以多线程下获取到的结果是不同的
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
// 初始化一个值反正不管什么值都不会是0。在此不需要考虑线程安全,再说了这个初始化本来就是针对当前线程是否是第一次才进来,同一个线程也不会存在并发的情况;
int probe = (p == 0) ? 1 : p; // skip 0
// 这个seeder因为是原子类型的对象,所以也是线程安全的获取到一个值。多个线程会获取不同的值,这样不同的线程中因为获取到的seed不同,最后生成的随机数也会不相同
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
// 针对线程来设置SEED的值
UNSAFE.putLong(t, SEED, seed);
// 会对线程来设置PROBE值
UNSAFE.putInt(t, PROBE, probe);
}
对于上面的代码我们了解了,在使用时直接通过ThreadLocalRandom.current() 这种方式来使用,它会根据当前线程设置SEED,PROBE值,这样保存了在next方法执行时,从UNSAFE.getLong(t,SEED)时获取到的值是当前线程的一个随机数;最后得到的也是一个随机数;
3. DefaultChannelId 类的源码
package io.netty.channel;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.MacAddressUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import static io.netty.util.internal.MacAddressUtil.defaultMachineId;
import static io.netty.util.internal.MacAddressUtil.parseMAC;
/**
* The default {@link ChannelId} implementation.
*/
public final class DefaultChannelId implements ChannelId {
private static final long serialVersionUID = 3884076183504074063L;
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
private static final byte[] MACHINE_ID;
private static final int PROCESS_ID_LEN = 4;
private static final int PROCESS_ID;
private static final int SEQUENCE_LEN = 4;
private static final int TIMESTAMP_LEN = 8;
private static final int RANDOM_LEN = 4;
private static final AtomicInteger nextSequence = new AtomicInteger();
/**
* Returns a new {@link DefaultChannelId} instance.
*/
public static DefaultChannelId newInstance() {
return new DefaultChannelId();
}
static {
int processId = -1;
String customProcessId = SystemPropertyUtil.get("io.netty.processId");
if (customProcessId != null) {
try {
processId = Integer.parseInt(customProcessId);
} catch (NumberFormatException e) {
// Malformed input.
}
if (processId < 0) {
processId = -1;
logger.warn("-Dio.netty.processId: {} (malformed)", customProcessId);
} else if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.processId: {} (user-set)", processId);
}
}
if (processId < 0) {
processId = defaultProcessId();
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.processId: {} (auto-detected)", processId);
}
}
PROCESS_ID = processId;
byte[] machineId = null;
String customMachineId = SystemPropertyUtil.get("io.netty.machineId");
if (customMachineId != null) {
try {
machineId = parseMAC(customMachineId);
} catch (Exception e) {
logger.warn("-Dio.netty.machineId: {} (malformed)", customMachineId, e);
}
if (machineId != null) {
logger.debug("-Dio.netty.machineId: {} (user-set)", customMachineId);
}
}
if (machineId == null) {
machineId = defaultMachineId();
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.machineId: {} (auto-detected)", MacAddressUtil.formatAddress(machineId));
}
}
MACHINE_ID = machineId;
}
private static int defaultProcessId() {
ClassLoader loader = null;
String value;
try {
loader = PlatformDependent.getClassLoader(DefaultChannelId.class);
// Invoke java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
Class<?> mgmtFactoryType = Class.forName("java.lang.management.ManagementFactory", true, loader);
Class<?> runtimeMxBeanType = Class.forName("java.lang.management.RuntimeMXBean", true, loader);
Method getRuntimeMXBean = mgmtFactoryType.getMethod("getRuntimeMXBean", EmptyArrays.EMPTY_CLASSES);
Object bean = getRuntimeMXBean.invoke(null, EmptyArrays.EMPTY_OBJECTS);
Method getName = runtimeMxBeanType.getMethod("getName", EmptyArrays.EMPTY_CLASSES);
value = (String) getName.invoke(bean, EmptyArrays.EMPTY_OBJECTS);
} catch (Throwable t) {
logger.debug("Could not invoke ManagementFactory.getRuntimeMXBean().getName(); Android?", t);
try {
// Invoke android.os.Process.myPid()
Class<?> processType = Class.forName("android.os.Process", true, loader);
Method myPid = processType.getMethod("myPid", EmptyArrays.EMPTY_CLASSES);
value = myPid.invoke(null, EmptyArrays.EMPTY_OBJECTS).toString();
} catch (Throwable t2) {
logger.debug("Could not invoke Process.myPid(); not Android?", t2);
value = "";
}
}
int atIndex = value.indexOf('@');
if (atIndex >= 0) {
value = value.substring(0, atIndex);
}
int pid;
try {
pid = Integer.parseInt(value);
} catch (NumberFormatException e) {
// value did not contain an integer.
pid = -1;
}
if (pid < 0) {
pid = PlatformDependent.threadLocalRandom().nextInt();
logger.warn("Failed to find the current process ID from '{}'; using a random value: {}", value, pid);
}
return pid;
}
private final byte[] data;
private final int hashCode;
private transient String shortValue;
private transient String longValue;
private DefaultChannelId() {
data = new byte[MACHINE_ID.length + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];
int i = 0;
// machineId
System.arraycopy(MACHINE_ID, 0, data, i, MACHINE_ID.length);
i += MACHINE_ID.length;
// processId
i = writeInt(i, PROCESS_ID);
// sequence
i = writeInt(i, nextSequence.getAndIncrement());
// timestamp (kind of)
i = writeLong(i, Long.reverse(System.nanoTime()) ^ System.currentTimeMillis());
// random
int random = PlatformDependent.threadLocalRandom().nextInt();
i = writeInt(i, random);
assert i == data.length;
hashCode = Arrays.hashCode(data);
}
private int writeInt(int i, int value) {
data[i ++] = (byte) (value >>> 24);
data[i ++] = (byte) (value >>> 16);
data[i ++] = (byte) (value >>> 8);
data[i ++] = (byte) value;
return i;
}
private int writeLong(int i, long value) {
data[i ++] = (byte) (value >>> 56);
data[i ++] = (byte) (value >>> 48);
data[i ++] = (byte) (value >>> 40);
data[i ++] = (byte) (value >>> 32);
data[i ++] = (byte) (value >>> 24);
data[i ++] = (byte) (value >>> 16);
data[i ++] = (byte) (value >>> 8);
data[i ++] = (byte) value;
return i;
}
@Override
public String asShortText() {
String shortValue = this.shortValue;
if (shortValue == null) {
this.shortValue = shortValue = ByteBufUtil.hexDump(data, data.length - RANDOM_LEN, RANDOM_LEN);
}
return shortValue;
}
@Override
public String asLongText() {
String longValue = this.longValue;
if (longValue == null) {
this.longValue = longValue = newLongValue();
}
return longValue;
}
private String newLongValue() {
StringBuilder buf = new StringBuilder(2 * data.length + 5);
int i = 0;
i = appendHexDumpField(buf, i, MACHINE_ID.length);
i = appendHexDumpField(buf, i, PROCESS_ID_LEN);
i = appendHexDumpField(buf, i, SEQUENCE_LEN);
i = appendHexDumpField(buf, i, TIMESTAMP_LEN);
i = appendHexDumpField(buf, i, RANDOM_LEN);
assert i == data.length;
return buf.substring(0, buf.length() - 1);
}
private int appendHexDumpField(StringBuilder buf, int i, int length) {
buf.append(ByteBufUtil.hexDump(data, i, length));
buf.append('-');
i += length;
return i;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public int compareTo(final ChannelId o) {
if (this == o) {
// short circuit
return 0;
}
if (o instanceof DefaultChannelId) {
// lexicographic comparison
final byte[] otherData = ((DefaultChannelId) o).data;
int len1 = data.length;
int len2 = otherData.length;
int len = Math.min(len1, len2);
for (int k = 0; k < len; k++) {
byte x = data[k];
byte y = otherData[k];
if (x != y) {
// treat these as unsigned bytes for comparison
return (x & 0xff) - (y & 0xff);
}
}
return len1 - len2;
}
return asLongText().compareTo(o.asLongText());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DefaultChannelId)) {
return false;
}
DefaultChannelId other = (DefaultChannelId) obj;
return hashCode == other.hashCode && Arrays.equals(data, other.data);
}
@Override
public String toString() {
return asShortText();
}
}