详解ThreadLocal

前言

本篇文章我来讨论一下什么是ThreadLocal以及它的实现原理。其底层数据结构有点相似HashMap,因此对HashMap不熟悉的朋友能够先去看一看我前面介绍HashMap的那篇文章。java

本文如如有不对或不实之处,也欢迎各位读者朋友评论指正,欢迎探讨交流。
数组

1、什么是ThreadLocal

我总结以后以为能够这样理解:

ThreadLocal提供了线程的局部变量,每一个线程均可以经过set()和get()来对这个局部变量进行操做,但不会和其余线程的局部变量进行冲突,实现了线程的数据隔离。安全

简而言之:ThreadLocal中填充的变量属于当前线程,该变量对其余线程而言是不可见的。

2、ThreadLocal实现的原理

咱们来翻一翻ThreadLocal的源码实现,这是学习理解一个工具类最简单有效的方法。

ThreadLocal,链接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。经过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操做。

ThreadLocalMap,用来存储数据,采用相似hashmap机制, 存储了以threadLocal为key,须要隔离的数据为value的Entry键值对数组结构。

1. ThreadLocal、ThreadLocal、Thread之间的关系

ThreadLocalMap是ThreadLocal内部类,由ThreadLocal建立,Thread有ThreadLocal.ThreadLocalMap类型的属性。

2. ThreadLoalMap

从名字上看,能够猜到它也是一个相似HashMap的数据结构,可是在ThreadLocal中,并没实现Map接口。

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //...
}复制代码
经过上面咱们能够发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储

咱们的值都是存储到这个Map上的,key是当前ThreadLocal对象!数据结构

若是该Map不存在,则初始化一个:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}复制代码
若是该Map存在,则从Thread中获取!

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
复制代码
Thread维护了ThreadLocalMap变量

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null复制代码
从上面又能够看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中。因而咱们能够总结出:Thread为每一个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是ThreadLocal对象自己,value则是要存储的对象。

在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每个key-value键值对,只不过这里的key永远都是ThreadLocal对象,经过ThreadLocal对象的set方法,结果把ThreadLocal对象本身当作key,放进了ThreadLoalMap中。并发


这里须要注意的是,ThreadLoalMap和HashMap很大的区别是,Entry中没有next字段,因此就不存在链表的状况了。若是出现hash冲突怎么办?先来看看set方法。

3. set() 方法

首先,咱们来看一下ThreadLocal的set()方法,由于咱们通常使用都是new完对象,就往里边set对象了

public void set(T value) {
    // 获得当前线程对象
    Thread t = Thread.currentThread();
    // 这里获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 若是map存在,则将当前线程对象t做为key,要存储的对象
    //做为value存到map里面去
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //根据hash值计算存放下标i
    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();
}
复制代码
在插入过程当中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程以下:
一、若是当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
二、若是位置 i 已经有Entry对象了,若是这个Entry对象的key正好是即将设置的key,那么从新设置Entry中的value;
三、很不巧,位置i的Entry对象,和即将设置的key不要紧,那么只能找下一个空位置;

这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,而后判断该位置Entry对象中的key是否和get的key一致,若是不一致,就判断下一个位置,能够发现,set和get若是冲突严重的话,效率很低。工具

4. 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;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
//查找对应key的 Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
} 复制代码

3、ThreadLocal原理总结

  1. 每一个Thread维护着一个ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  3. 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
  4. 调用ThreadLocal的get()方法时,实际上就是在ThreadLocalMap获取值,key是ThreadLocal对象
  5. ThreadLocal自己并不存储值,它只是做为一个key来让线程从ThreadLocalMap获取value。
正是上面的这些原理,因此ThreadLocal可以实现“数据隔离”,获取当前线程的局部变量值,不受其余线程影响。

4、总结

ThreadLocal设计的目的就是为了可以在当前线程中有属于本身的变量,实现不一样线程间的数据隔离,线程间的数据互不干扰。ThreadLocal是解决线程安全问题一个很好的思路,它经过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
相关文章
相关标签/搜索