Java多线程之ThreadLocal

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~

一、ThreadLocal实现原理

set(T value)

    public void set(T value) {

        // 得到当前线程对象
        Thread t = Thread.currentThread();
        
        // 这里获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);

        // 如果map存在,则将当前对象threadLocal作为key,要存储的对象作为value存到map里面去
        if (map != null)
            map.set(this, value);
        else
            // 如果map不存在,则在当前线程中创建出一个ThreadLocalMap!
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    static class ThreadLocalMap {

        /**
        ThreadLocalMap是ThreadLocal的一个内部类,它用Entry(内部是数组)来进行存储。我们的value直接存储到这个Map,key是当前的ThreadLocal。
         */
        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中使用内部类来编写的,但对象的引用是在Thread中

     //ThreadLocalMap是在Thread中维护的。
    ThreadLocal.ThreadLocalMap threadLocals = null

主要实现步骤:

  • 获取当前线程对象,获取当前线程对象的ThreadLocalMap
    • threadLocalMap若存在,设置一个值;否则就给线程创建一个threadLocalMap
    private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            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();
        }
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

设置的主要逻辑:

  1. 根据ThreadLocal的threadLocalHashCode取模获取到一个table中的位置
  2. 如果有数据,获取这个位置上的ThreadLocal
    • 判断这个ThreadLocal与传入来的是否是同一个,如果是,返回value
    • 不是,再判断一下位置上的ThreadLocal是是不是空的( Entry是ThreadLocal弱引用,有可能这个ThreadLocal被垃圾回收了),这时候把新设置的value替换到当前位置上,返回
    • 上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。(当table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据)
      get()
     public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        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);
        }
    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;
        }

主要步骤:

  • 获取当前线程
  • 尝试去当前线程中拿它的ThreadLocal.ThreadLocalMap
  • 判端ThreadLocalMap是否存在
    1. 有就尝试根据当前ThreadLocal的threadLocalHashCode取模去table中取值,有就返回,没有就给模加1继续找
    2. 没有就调用set方法给当前线程ThreadLocal.ThreadLocalMap设置一个初始值

remove()

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

尝试找到当前线程的ThreadLocalMap,如果找到了就移除。

二、小结

  • 每个Thread维护着一个ThreadLocalMap,ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  • ThreadLocal通过开放地址法,将自己和value设置到ThreadLocalMap中,ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value
  • 每次set的时候往线程里面的ThreadLocal.ThreadLocalMap中的table数组某一个位置塞一个值,这个位置由ThreadLocal中的threadLocaltHashCode取模得到,如果位置上有数据了,就往后找一个没有数据的位置
  • get同上
  • 既然ThreadLocal没有key,那么一个ThreadLocal只能塞一种特定数据。如果想要往线程里面的ThreadLocal.ThreadLocalMap里的table不同位置塞数据 ,比方说想塞三种String、一个Integer、两个Double、一个Date,请定义多个ThreadLocal,ThreadLocal支持泛型"public class ThreadLocal<T>"

参考:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容