并发_ThreadLocal、InheritableThreadLocal、ThreadLocalRandom

ThreadLocal

ThreadLocalJDk包提供的,它提供了线程的本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。

当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

使用示例

public class ThreadLocalTest {
   private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
   private static void print(String str) {
      System.out.println(str + ":" + threadLocal.get());
      threadLocal.remove();
   }
    
   public static void main(String[] args) {
      new Thread(() -> {
         threadLocal.set("test t1 ThreadLocal variable");
         print("t1");
         System.out.println("t1 remove after:" + threadLocal.get());
      }).start();

      new Thread(() -> {
         threadLocal.set("test t2 ThreadLocal variable");
         print("t2");
         System.out.println("t2 remove after:" + threadLocal.get());
      }).start();
   }
}

/*
t1:test t1 ThreadLocal variable
t2:test t2 ThreadLocal variable
t1 remove after:null
t2 remove after:null
*/

源码

Thread 类中有两个变量

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在在调用线程的threadLocals变量里面。

ThreadLocal就是一个工具壳,通过set方法把value值放入线程的threadLocals变量里面并存放起来,当调用线程的get方法时,再从当前线程的threadLocals变量里面取出。

如果线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以不需要本地变量的时可以通过remove方法将其删除。

set

public void set(T value) {
    Thread t = Thread.currentThread();
    // 获取当前线程的 threadLocals 变量
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
    // 第一次调用时就创建 ThreadLocalMap
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // threadLocals 变量为空,则初始化当前线程的 threadLocals 变量
    return setInitialValue();
}

private T setInitialValue() {
    // 初始化 value 为 null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}

remove

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

InheritableThreadLocal

同一个ThreadLocal变量在父线程中被设置后,在子线程中是获取不到的。

InheritableThreadLocal可以解决这个问题。

使用示例

public class InheritableThreadLocalTest {

   private static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

   public static void main(String[] args) {
      inheritableThreadLocal.set("hello world");
      new Thread(() -> {
         // 输入 inheritableThreadLocal 中的值
         System.out.println("thread: " + inheritableThreadLocal.get());
      }).start();
      System.out.println("main: " + inheritableThreadLocal.get());
      // 习惯性回收不用的变量
      inheritableThreadLocal.remove();
   }
}
/*
main: hello world
thread: hello world
*/

源码

InheritableThreadLocal重写了三个方法childValuegetMapcreateMap,使用了Thread的变量inheritableThreadLocals.

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

childValueThread的构建方法调用时执行。

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
    // 获取父线程
    Thread parent = currentThread();
    // ......
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    // 获取父线程 ThreadLocalMap 的所有 Entry
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    // Set the resize threshold to maintain at worst a 2/3 load factor.
    setThreshold(len);
    // 初始化当前线程的 table 值
    table = new Entry[len];
    // 遍历父线程的 table
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                // 调用 InheritableThreadLocal 重写的方法 childValue
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                // 将新生成的 Entry 方到当前线程的 table 中
                table[h] = c;
                size++;
            }
        }
    }
}

Random

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // 1.根据老的种子生成新的种子
    int r = next(31);
    // 2.根据新的种子计算随机数
    int m = bound - 1;
    if ((bound & m) == 0)  // i.e., bound is a power of 2
        r = (int)((bound * (long)r) >> 31);
    else {
        for (int u = r; u - (r = u % bound) + m < 0; u = next(31))
            ;
    }
    return r;
}

// 采用CAS的方式生成新的种子,多线程下进行CAS只会有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能
protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

ThreadLocalRandom

为了弥补Random的缺陷。新增了ThreadLocalRandom,在JUC包下。

ThreadLocalRandom继承了Random,并重写了nextInt等方法,没有使用父类的原子性种子变量。

ThreadLocalRandom中并没有存放具体的种子,而是存放在Thread中的threadLocalRandomSeed变量里面。

当线程调用ThreadLocalRandomcurrent方法时,会初始化此种子变量。

主要代码实现逻辑

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
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);
    }
}

current

public static ThreadLocalRandom current() {
    // 判断 threadLocalRandomProbe 是否为0
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        // 计算当前线程的初始化种子变量
        localInit();
    return instance;
}

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

nextInt

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // 根据当前线程种的种子计算新种子
    int r = mix32(nextSeed());
    // 根据新种子和bound计算随机数
    int m = bound - 1;
    if ((bound & m) == 0) // power of two
        r &= m;
    else { // reject over-represented candidates
        for (int u = r >>> 1; u + m - (r = u % bound) < 0;  u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

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)获取当前线程中threadLocalRandomSeed变量的值,然后在种子的基础上加GAMMA值作为新种子。

然后使用UNSAFE.putLong把新种子放入当前线程的threadLocalRandomSeed变量中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349