ThreadLocal详解及线程数据共享(通过反射)

75 阅读5分钟

ThreadLocal是java.lang下面的一个类,为每个线程创建一个共享变量的副本来保证各个线程的变量访问和修改互不冲突

这样我们就能在代码中利用其统一的api,来创建不同线程的不同副本,从而便于代码逻辑开发

ThreadLocal内保存的内容是线程内共享,不同线程间相互隔离的

那么他的原理是什么

每个线程都有各自的ThreadLocalMap实例(存/取数据的真正地方),这是每个线程都有独立副本的根本原因

Thread类内部维护了一个ThreadLocalMap成员变量(k:ThreadLocal,v:Entry)和inheritableThreadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;(稍后会讲)

这个成员变量在ThreadLocal类里我们去看一下源码

显然其底层是一个键值对集合

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;

当我们调用threadLocal.set()时会发生什么

可以看到他会获取当前线程对象,然后获取对象的ThreadLocalMap的引用

如果不为空 则将threadlocal对象-value放入这个集合

否则初始化这个集合再放入(懒加载,防止内存浪费)

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

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

那么两个线程都调用threadLocal.set()

实际是获取他们各自线程对象所维护的一个集合,在该集合进行操作

那threadlocal.get() 自然也就是通过线程对象获取对应集合,然后通过threadlocal找到对应的值了

看一下源码,他还做了些安全性操作 这里不再赘述

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

所以在多线程中,每个线程都能通过同一个ThreadLocal对象,存储和获取到各自的副本数据

同时可以看到,一个Entry实例通过弱引用(当堆内一个对象只被弱引用引用时,gc的时候他将会被回收掉)指向了一个ThreadLocal对象

强引用指向了一个Object对象(ThreadLocal对象关联的值)
为什么对ThreadLocal对象要采用弱引用呢

其实这是为了解决弱引用问题的

我们先来描绘一下ThreadLocal的堆栈图

可以看到ThreadLocal存在线程栈上对他的引用

如果ThreadLocal引用不指向ThreadLocal了,我们可能认为他会被自动回收,但是线程仍然存活的话 ThreadLocalMap就不会被回收

Entry对象也就存在,如果Entry对象持有的是ThreadLocal的强引用的话,那么ThreadLocal对象就会一直存在,久而久之可能导致oom

Entry对象对value存在一个强引用,后续ThreadLocal不用了,该value就没意义了,那么怎么清理他呢,我们要保证 ThreadLocal在的时候 value在 threadLocal不需要了,value就回收

ThreadLocal有这么一个api 如果th引用没有指向ThreaLocal对象或者其他情况,那么调用remove() 就会把键为空的键值对清理掉


static  ThreadLocal<Integer> th =new ThreadLocal<>();

th.remove();

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

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.refersTo(key)) {
                    e.clear();//清掉key的弱引用
                    expungeStaleEntry(i);//清理key为null的键值对
                    return;
                }
            }
        }

同时在调用get()set()的时候也会有清理动作 来防止内存泄漏

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);

    Entry e = table[i];

    if (e != null && e.refersTo(key))

        return e;

    else

        return getEntryAfterMiss(key, i, e); // 会清理 key为null的键值对

}

前面提到的inheritableThreadLocals和threadLocals基本一致

不过当我们在某个线程a环境下new一个线程的时候,他会判断当前线程a的inheritableThreadLocals是否为空

不为空,则将键值对拷贝给他

从而实现了父子之间的值传递

public Thread() {
    this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    this(group, target, name, stackSize, null, true);
}


 private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
//省略...

//获取当前线程(创建者a)
Thread parent = currentThread();

//省略..
//如果a的inheritableThreadLocals 被初始化了 就将
      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 */
        this.tid = nextThreadID();
    }





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

//创建一个一模一样的集合
   private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
//默认返回父亲的值 同时我们能继承inheritableThreadLocals 对方法进行重写,修改父子通信逻辑
                        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++;
                    }
                }
            }
        }

但是我们发现,父子持有的不是同一个副本,只是数据共享

我们可以继承Thread,修改其内部逻辑,实现真正意义上的数据共享

写的途中我发现这个字段是私有的读取不了 那就采用反射 注意jak8版本后的Java.lang包下反射会报错

static class MyThread extends Thread{

    public MyThread(Runnable target) throws NoSuchFieldException, IllegalAccessException {
        super(target);
        //获取Thread类的类对象
        Field field = Thread.class.getDeclaredField("inheritableThreadLocals");
        Class<? extends MyThread> clazz= this.getClass();
        Thread parents= Thread.currentThread();
        field.setAccessible(true);
        Object parentsMap= field.get(parents);
        field.set(this,parentsMap);
        field.setAccessible(false);
    }
}