ThreadLocal-从源码角度简单分析

ThreadLcoal源码浅析

我们知道ThreadLocal用于维护多个线程线程独立的变量副本,这些变量只在线程内共享,可跨方法、类等,如下是一个维护多个线程Integer变量的ThreadLocal

ThreadLocal<Integer> threadLocalNum = new ThreadLocal<>();

每个使用threadLocalNum的线程,可以通过形如threadLocalNum.set(1)的方式创建了一个独立使用的Integer变量副本,那么它是怎么实现的呢?我们今天就来简单的分析一下。

先看下ThreadLocal的set方法是如何实现的,源码如下:

public void set(T value) { Thread t = Thread.currentThread(); //获取当前线程 ThreadLocalMap map = getMap(t); //获取当前线程的ThreadLocalMap if (map != null) map.set(this, value); //当前线程的ThreadLocalMap不为空则直接设值 else createMap(t, value); //当前线程的ThreadLocalMap为空则创建一个来设置值 }

是的,你没有看错,是获取当前线程中的ThreadLocalMap来设置的值,我们来看一下getMap(t)是如何实现的:

ThreadLocalMap getMap(Thread t) { return t.threadLocals; }

然后我们看到Thread中包含了一个ThreadLocalMap类型的属性:

ThreadLocal.ThreadLocalMap threadLocals = null;

到这里我们可以得出一个结论:各个线程持有了一个ThreadLocalMap的属性,通过ThreadLocal设置变量时,直接设置到了对应线程的的ThreadLocalMap属性中

那么不同的线程中通过ThreadLocal设置的值是如何关联定义的ThreadLocal变量和Thread中的ThreadLocalMap的呢?我们接着分析。

前面写到当前线程的ThreadLocalMap为空则创建一个ThreadLocalMap来设值,我们来看下createMap(t, value)的具体实现:

void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /////////////////// //ThreadLocalMap构造器定义如下 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } private static final int INITIAL_CAPACITY = 16;

线程中threadLocals是一个ThreadLocalMap变量,其默认值是null,该线程在首次使用threadLocal对象调用set的时候通过createMap(Thread t, T firstValue)实例化。

先来看一下ThreadLocalMap,它是在ThreadLocal中定义的一个静态内部类,其内属性如下:

/** * 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 Entry[] table,用于存储通过threadLocal set 进来的变量,Entry定义如下:

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }

Entry继承了WeakReference<ThreadLocal<?>>,ThreadLocal在构造器中被指定为弱引用super(k)(后面会单独讨论为何这里使用弱引用)。

至此,我们可以知道ThreadLocal和Thead的内存结构如下:

ThreadLocal-从源码角度简单分析

ThreadLocal的垃圾回收

网上看到很多文章都在讲ThreadLocal的内存泄露问题,所以也在这里简单说一下自己的理解。

从上面的结构可以看出ThreadLocal涉及到的要回收的对象包括:

ThreadLocal实例本身

各线程中的threadLocalMap,其中包括各个Entry的 key, value

下面先简述java的引用,然后分别讨论ThreadLocal本身的回收和threadLcoalMap的回收

Java引用

强引用(StrongReference):对象可达就不会被gc回收,空间不足时报error

软引用(SoftReference):对象无其他强引用,当空间不足时才会被gc回收。

弱引用(WeakReference):对象无其他强引用,gc过程扫描到就会被回收。

ThreadLocal的回收

ThreadLocal实例的引用主要包括两种:

ThreadLocal定义处的强引用

各线程中ThreadLocalMap里的key=weak(threadLocal), 是弱引用

强引用还在的情况下ThreadLocal一定不会被回收;无强引用后,由于各个Thread中Entry的key是弱引用,会在下次GC后变为null。ThreadLocal实例什么时候被回收完全取决于强引用何时被干掉,那么什么时候强引用会被销毁呢?最简单的就是 threadLocal=null强引用被赋值为null;其它也可是threadLocal是一个局部变量,在方法退出后引用被销毁,等等。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwgdyg.html