ThreadLocal

ThreadLocal(线程变量).每个线程获取与填充都操作当时线程对应的变量副本. 不同线程间的变量副本是阻隔开的.

static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
    threadLocal.set("main");
    new Thread(()->{
        threadLocal.set("thread1");
    }).start();
    new Thread(()->{
        threadLocal.set("thread2");
        System.out.println("thread2:"+threadLocal.get());
    }).start();
    System.out.println("main:"+threadLocal.get());
}
控制台:
thread2:thread2
main:main

从功用上来ThreadLocal或许是一个容器,key是线程id,value是线程的具有的目标数据. 假如我规划的话或许ThreadLocal会规划成上述这种.

MyThreadLocal:

  public class MyThreadLocal<T>{
    Map<Thread, T> local= new HashMap<Thread, T>();
    T get(){
        Thread thread = Thread.currentThread();
        T o = local.get(thread);
        return o;
    }
    void set(T t){
        Thread thread = Thread.currentThread();
        local.put(thread, t);
    }
}
public static void main(String[] args) throws InterruptedException {
    MyThreadLocal<String> threadLocalUtil = new MyThreadLocal<>();
    threadLocalUtil.set("main");
    System.out.println(threadLocalUtil.get());
    new Thread(()->{
        threadLocalUtil.set("thread2");
        System.out.println("thread2:"+threadLocalUtil.get());
    }).start();
}

来看看这种完成的缺点:

  • ThreadLocal便是为多线程所规划的,所以一定涉及并发问题.HashMap线程并不是安全的,所以假如这样完成也要用ConcurrentHashMap<Thread, String> map = new ConcurrentHashMap<>();

  • ThreadLocal的思维是每个线程持有自己的副本,不存在竞赛联系. 上面的完成无论是用hashMap,还是ConcurrentHashMap,或者是锁 都会形成资源的竞赛.

  • 假如在主线程中new MyThreadLocal();. 只需主线程一向存在,MyThreadLocal引证就一向在,即便使用过MyThreadLocal的子线程现已完毕了 .map容器中子线程持有的内容也不会被GC掉. local目标会越来越大.

点进原码看看真正的ThreadLocal是怎样规划的:

  • ThreadLocalMap: ThreadLocalMap是ThreadLocal的静态内部类,承继了WeakReference的一个map结构.(WeakReference:弱引证只需产生的gc 目标就会被收回.详细可以看下blog.csdn.net/javaphpsqlm…)
  • threadLocals: ThreadLocal的私有目标,类型是ThreadLocalMap. 数据都存放在各自线程的私有目标的这个map里边,一切称之为数据副本,线程间互相阻隔.

从set办法开端,线程初次调用ThreadLocal的set办法时threadLocals是空的,走到createMap. new了一个ThreadLocalMap.

用ThreadLocal目标set值:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

获取当时线程的私有目标threadLocals

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

创建一个map 并将值存到这个map中

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

new Entry(firstKey, firstValue)可以看到这个map的key便是ThreadLocal目标自身. value便是需要保存的值 key是ThreadLocal目标自身是由于每个线程可以使用多个ThreadLocal去保存值(

  • ThreadLocal threadLocal0 = new ThreadLocal<>();
  • ThreadLocal threadLocal1 = new ThreadLocal<>();
  • threadLocal0.set(int)
  • threadLocal0.set(string)

)

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

set办法搞清楚了 get办法也都能想到.便是去thread的私有目标threadLocalMap中通过当时调用的threadLocal目标作为key取值.

整体了解了ThreadLocal咱们再回忆看下完成细节

为什么要承继弱引证WeakReference.

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

那咱们反过来看下强引证会产生什么,咱们体系中或许会持续运行多个线程,体系中的线程又大部分来源于线程池的复用,线程的周期很长.每个线程中都运行如下代码ThreadLocalMap会存放了很多数据.

public  void systemOut() {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("BIG_DATA");
    System.out.println(threadLocal.get());
}

办法执行完毕了 jvm的栈中threadLocal目标现已没了. 可是ThreadLocalMap仍然持有key和”BIG_DATA”的强引证, 一切不会被GC掉.ThreadLocalMap数据就会越来越大

Entry承继了WeakReference便是为了处理这个问题,把key设置成弱引证.一旦threadLocal的强引证消失,下一次废物收回ThreadLocalMap中的key就会被gc掉.注意这里只有key是弱引证. value不是,value不会被gc.

后续get set时会触发一些清除操作,将key为null的value remove掉. 可是这个并不是固定的,在使用ThreadLocal时大家还是要在不需要的时分手动remove掉,尽或许的阻止内存溢出.

Java根底-ThreadLocal

从图来看会明晰一点,看下弱引证的作用便是为了当threadLocal为null也便是强引证1不存在的时分.可以吧key1废物收回起来

来考虑个小问题

value不会被gc或许会形成内存溢出, 为什么不参阅key把key、value都搞成弱引证.没有强引证时都给Gc掉

public static void main(String[] args) {
    //假定key value都是弱引证
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("main");
    new Thread(()->{
        threadLocal.set("thread1");
    }).start();
    //模仿jvm产生了废物收回
    System.gc();
    System.out.println("main:"+threadLocal.get());
}
控制台:
main:null

上面的代码假如value也是弱引证的话,threadLocal.get()取出来的便是空的了. 由于main办法没完毕ThreadLocal threadLocal目标的引证还在栈中,key不会被收回. 可是value也便是字符串”main”没强引证指向他了直接被废物收回了!

下面咱们继续看下用于储存的数据结构ThreadLocalMap中的细节

ThreadLocalMap的内部类:Entry

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {

ThreadLocalMap的构造函数

  • new一个初始巨细为Entry16数组

  • 核算key的hashCode与数组长度与运算得到下标. threadLocalHashCode里边的内容大致和.hashCode差不多,只不过threadLocalHashCode的散列性更好

  • 设置巨细为初始值1

  • 设置阀值,调整巨细阈值以在最坏情况下保持2/3的负载系数。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
/**
 * Set the resize threshold to maintain at worst a 2/3 load factor.
 */
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

hash算法大概是每次使用AtomicInteger原子性的的增加0x61c8864.可以使生成的哈希值满足涣散

 private static final int HASH_INCREMENT = 0x61c88647;
     private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

ThreadLocal的set办法

  • Entry e = tab[i]; e != null;命中了这个条件说明虽然hash满足涣散仍然产生了hash碰撞.
  • e = tab[i = nextIndex(i, len)]通过线性寻址法找到下一个空的方位.把数据存进去 e.value = value;
  • 线性寻址set和get都会用到.在寻址的过程中会识别到null key的数据,便是前边说到的弱引证.将key为空的元素删除,其他数据向前移动.
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

线性寻址:当时方位有抵触,就去按顺序依次寻找当时方位+1、+2、+3的地方是否是空的, 假如到了数组的末尾就从0开端直到找到空余方位. 线性寻址是最为简略直接的hash抵触处理方法.其他还有二次寻址、链表、双重散列等.

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

ThreadLocal没有承继联系,也便是子线程拿不到父线程的副本. InheritableThreadLocal弥补了这个功用

ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("main");
new Thread(()->{
    System.out.println("thread2:"+inheritableThreadLocal.get());
}).start();
控制台输出 main
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("main");
new Thread(()->{
    //掩盖父线程内容
    inheritableThreadLocal.set("thread2");
    System.out.println("thread2:"+inheritableThreadLocal.get());
}).start();
控制台输出 thread2

InheritableThreadLocal承继了ThreadLocal 重写了createMap等办法.inheritableThreadLocals取代了ThreadLocal中的目标

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

看一眼Thread的构造函数 ,其中有段逻辑是判别当时线程inheritThreadLocals是否有值有的话调用ThreadLocal.createInheritedMap批量仿制曩昔

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

由于是创建线程时仿制进去的,在子线程new出来之后,父线程向inheritableThreadLocal录入的数据子线程是不会拿到的

ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
new Thread(()->{
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("thread2:"+inheritableThreadLocal.get());
}).start();
inheritableThreadLocal.set("main");
控制台:
thread2:null

InheritableThreadLocal的使用:

  • 子线程需要父线程的登录态信息
  • 日志链路id等