再看ThreadLocal

原创
2018/09/03 18:02
阅读数 4.5K

ThreadLocal,网上文章很多,大家也基本都会使用,但是不一定用的好,或者说不一定真的能理解

ThreadLocal

ThreadLocal本身并不作为存储的容器,而是把值存放在当前线程中的变量里面,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;

有人会奇怪为什么会有2个对象,threadLocals就是我们一般情况使用的,而inheritableThreadLocals是供在当前线程又开了一个子线程时用的,这样可以使子线程也可以使用当前线程的threadLocals对象,需要配合使用ThreadLocal的子类InheritableThreadLocal使用,具体看下一节

threadLocals为空,翻遍了Thread内的源码,你也找不到给他赋值的地方,因为赋值的地方在ThreadLocal这个类里面:

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

如上所示,给当前thread的threadLocals变量赋值,并且key是this,也就是当前的ThreadLocal对象

注意到使用的是ThreadLocalMap,这个类很像HashMap,但是它并未实现任何接口,这个类在ThreadLocal里面,虽然定义成成static class ThreadLocalMap,但是它的方法都是private的,意味着这个类只供ThreadLocal使用

其内部使用自定义的Entry对象来存储数据,Entry类如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

继承了WeakReference,看构造方法,可以知道key是WeakReference类型,value是普通的强引用

这意味着,如果没有其他引用,那么线程结束后,key会被自动回收,也即ThreadLocal会被回收,看起来很完美

但是一般情况下,我们的使用方法,都是类似如下:

public abstract class ThreadContext {

    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<>();

}

使用static修饰,那么即使线程结束,依然有引用,所以不会被回收

而且,很多时候我们使用线程池,可能线程永远都不会结束,那么ThreadLocal对象也就永远不会被回收

上述两种情况都有可能发生内存泄漏,而且会出现逻辑错乱的现象,所以最佳实践就是:在使用完后,显示的调用ThreadLocal的remove方法

InheritableThreadLocal

InheritableThreadLocal的一般使用方法如下:

public class Test {
 
    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
 
    public static void main(String args[]){
        threadLocal.set(new Integer(123));
 
        Thread thread = new MyThread();
        thread.start();
 
        System.out.println("main = " + threadLocal.get());
    }
 
    static class MyThread extends Thread{
        @Override
        public void run(){
            System.out.println("MyThread = " + threadLocal.get());
        }
    }
}

输出:

main = 123

MyThread = 123

子线程完美的使用了父线程的对象,这是怎么实现的呢?

InheritableThreadLocal类很简单,源码如下:

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

继承了ThreadLocal,覆盖了父类的getMap方法,返回的是Thread类的inheritableThreadLocals成员变量,也就是我们之前提到的Thread累里面的另一个成员变量

同时也覆盖了createMap方法,赋值的也是父类的inheritableThreadLocals成员变量

可以看到InheritableThreadLocal里面操作的都是父类的inheritableThreadLocals了,和Thread的成员变量threadLocals没有任何关系了,这里其实用到了模板模式,父类定义了一个流程,子类按照父类的框架执行一个定义好的流程,只是一些细节可以有自己的实现

我们看一下Thread的构造方法,会调用各种init方法,这些init方法最终都是调用下面的init方法,而且最后一个参数inheritThreadLocals为true(严谨一点,Thread(Runnable target, AccessControlContext acc)这个构造方法调用init方法的inheritThreadLocals为false,但是该方法不是public的)

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
  ...
    Thread parent = currentThread();
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  ...
}

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

和inheritableThreadLocals相关的代码如上,如果inheritThreadLocals为true,并且当前线程的inheritableThreadLocals值不为空,那么给inheritableThreadLocals赋值,我们再看一下调用的new ThreadLocalMap构造方法

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            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) {
                        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);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

可看到,是一个遍历赋值的操作,看过HashMap源码的都知道,里面的Entry是一个链表,因为会有key重复的情况,这里为什么没有呢?

大家看16,17行,如果位置为h的已经有值了,那么死循环,重新生成位置h,直到该位置没有值,所以不需要链表

这样子线程就有用父线程的对象了,需要注意的是,第13行,Object value = key.childValue(e.value);

这个方法在ThreadLocal里如下:

T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

因为这个方法就不是给他用的,而是给他的子类InheritableThreadLocal用的,该方法在InheritableThreadLocal覆盖如下:

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

只是简单的返回参数,一般情况下够用了,但是如果你的需求是想子线程和父线程的值可以各自修改而不受影响,那么你们可以继承InheritableThreadLocal这个类,覆盖其childValue方法,在该方法里你就可以进行深度拷贝的操作,注意是深度拷贝

其他

通过如上分析,也可以看出来

  1. 可以再同一个线程里同时使用ThreadLocal和InheritableThreadLocal,他们对应的是Thread里的不同变量,互不影响,只是InheritableThreadLocal可以被子类继承
  2. 可以使用多个ThreadLocal对象,互不影响,因为源码里的ThreadLocalMap的key是ThreadLocal本身,所以,很多人没看ThreadLocal源码前,可能会以为key是当前Thread,如果是的话,就不能同时使用多个ThreadLocal对象了,后面的覆盖前面的
  3. 使用InheritableThreadLocal时可以覆盖childValue方法,进行深度拷贝
展开阅读全文
打赏
6
60 收藏
分享
加载中
打赏
1 评论
60 收藏
6
分享
返回顶部
顶部