Netty源码中知识点学习记录3.1----DefaultChannelId

###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################

本文中关联的所有文章的总目录可以参看:系列文章目录

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这个来获取的;

  1. t是Thread.currentThread(), 这个是根据每个线程都不一样的
  2. 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);
        }
    }
  1. 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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值