为了账号安全,请及时绑定邮箱和手机立即绑定

ThreadLocal实现分析

标签:
Docker

ThreadLocal 可以让用户很方便的保存线程隔离的变量,每个线程只能保存一份,多次set新值会覆盖旧值。下面分几个角度来分析一下ThreadLocal。

使用方法

简单附上使用方法:

public class TestThreadLocal {    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();    public static Object lock = new Object();    public static void main(String[] args) {
        threadLocal.set("a string run in main");
        Thread t = new Thread(new Runnable() {            @Override
            public void run() {
                TestThreadLocal.threadLocal.set("a string run in sub");                synchronized (TestThreadLocal.lock){                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("sub thread get: "+threadLocal.get());
            }
        });
        t.start();        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        synchronized (TestThreadLocal.lock){
            lock.notify();
        }
        System.out.println("main thread get: " +threadLocal.get());
    }
}

运行结果

main thread get: a string run in main
sub thread get: a string run in sub

Process finished with exit code 0

可以看到两个线程间设置的值互不影响,均可以取出正确值。

源码解析

ThreadLocal主要有3个方法,get,set,remove。我们只要了解了插入的原理,另外两个自然不攻自破,附上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);
    }    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;
    }

主要结构是ThreadLocalMap,把它的数据部分以及set方法贴出来。

    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;        /**
         * The number of entries in the table.
         */
        private int size = 0;        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        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();
        }

使用之前例子中的情况,可以看到,当我们调用set方法时,实际是以threadLocal变量本身和这个“a string run in main”字符串生成一个Entry,根据对象的hash值计算位置,放到table数组中。

一些细节

线程安全性。

ThreadLocal变量是存储在线程对象中的,即对于同一个ThreadLocal变量,在不同的线程中操作时,修改的不是同一个内存空间,因此ThreadLocal是线程安全的类。

泛化存储类型

ThreadLocal<Integer> 和 ThreadLocal<String>都是放在同一个Thread相关联的map中,类型不一样怎么存储?因此需要在调用set方法的时候将存入的对象转为Object对象,在调用get方法的时候再转换回来。

如何保证hash冲突后功能的正确性(重点)

首先我们可以看到THreadLocal处理冲突的算法:如果hash冲突了,ThreadLocal采取的是线性探测法。
考虑set方法:
从hash值的位置往后寻找到最近的一个可用位置(可用即当前位置为null,table结尾位置也不为null的话将从位置0继续查找,直到找到)。
考虑get方法:
调用get方法时,如果hash值对应的位置非空,但存储的引用和当前的ThreadLocal对象不一致,那么还有可能是因为冲突了存储在这个位置以后,从当前位置往后查找,依次对比是否是当前ThreadLocal对象,找到则返回,遇到null则说明没找到。

这对ThreadLocal提出了两点要求:

  1. table不能是满的,否则get一个未set的ThreadLocal对象将死循环。
    这个问题好解决,ThreadLocal的rehash因子是2/3,因此无论何时table必定有位置的值是null。

  2. 具有相同hash值的对象,存储必须是连续的,即中间不能有null间隔开来。这就要求在remove一个对象的时候,需要将这个空位以后直到null中间的值都重新计算位置。

弱引用

java中GC的方法是判断对象的引用计数是否等于0,等于0则回收这个对象。正常情况下我们调用如下代码将产生强引用,两句代码执行过后,threadlocal指向的对象的引用计数是2。弱引用的作用就是不参与引用计数的计算。使用弱引用保证了当使用者将ThreadLocal对象置空或指向一个新的对象时,旧对象能得到被GC的机会。
同时我们也就可以解释ThreadLocal代码中多处replaceStaleEntry,expungeStaleEntry(清除过期节点)的原因。

ThreadLocal<String> threadlocal = new ThreadLocal<>();

ThreadLocal<String> threadlocal2 = threadlocal;

InheritableThreadLocal

InheritableThreadLocal继承自ThreadLocal,它和ThreadLocal不同的是,父线程保存的InheritableThreadLocal变量可以在子线程中存留。这个是如何实现的呢?

从以下Thread类的代码可以看到(最后几行),线程类中有一个成员变量inheritableThreadLocals,在构造函数中,会调用init方法,里面以父类的inheritableThreadLocals对象为模版构造了一份子类的inheritableThreadLocals。

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    private void init(ThreadGroup g, Runnable target, String name,                      long stackSize, AccessControlContext acc,                      boolean inheritThreadLocals) {        if (name == null) {            throw new NullPointerException("name cannot be null");
        }        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();        if (g == null) {            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();        /*
         * Do we have the required permissions?
         */
        if (security != null) {            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();        this.group = g;        this.daemon = parent.isDaemon();        this.priority = parent.getPriority();        if (security == null || isCCLOverridden(parent.getClass()))            this.contextClassLoader = parent.getContextClassLoader();        else
            this.contextClassLoader = parent.contextClassLoader;        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();        this.target = target;
        setPriority(priority);        if (inheritThreadLocals && 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();
    }



作者:msrpp
链接:https://www.jianshu.com/p/4ba09bfad028


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消