ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇。

比如

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

in thread 1

//in thread1
treadLocal.set("value1");
.....
//value的值是value1
String value = threadLocal.get();

in thread 2

//in thread2
treadLocal.set("value2");
.....
//value的值是value2
String value = threadLocal.get();

不论thread1和thread2是不是同时执行,都不会有线程安全问题,我们来测试一下。

线程安全测试

开10个线程,每个线程内都对同一个ThreadLocal对象set不同的值,会发现ThreadLocal在每个线程内部get出来的值,只会是自己线程内set进去的值,不会被别的线程影响。

static void testUsage() throws InterruptedException {
Utils.println("-------------testUsage-------------------");
ThreadLocal<Long> threadLocal = new ThreadLocal<>(); AtomicBoolean threadSafe = new AtomicBoolean(true);
int count = 10;
CountDownLatch countDownLatch = new CountDownLatch(count);
Random random = new Random(736832);
for (int i = 0; i < count; i ++){
new Thread(() -> {
try {
//生成一个随机数
Long value = System.nanoTime() + random.nextInt();
threadLocal.set(value);
Thread.sleep(1000); Long value2 = threadLocal.get();
if (!value.equals(value2)) {
//get和set的value不一致,说明被别的线程修改了,但这是不可能出现的
threadSafe.set(false);
Utils.println("thread unsafe, this could not be happen!");
}
} catch (InterruptedException e) { }finally {
countDownLatch.countDown();
} }).start();
} countDownLatch.await(); Utils.println("all thread done, and threadSafe is " + threadSafe.get());
Utils.println("------------------------------------------");
}

输出:

-------------testUsage------------------
all thread done, and threadSafe is true
-----------------------------------------

原理浅析

翻开ThreadLocal的源码,会发现ThreadLocal只是一个空壳子,它并不存储具体的value,而是利用当前线程(Thread.currentThread())的threadLocalMap来存储value,key就是这个threadLocal对象本身。

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

Thread的threadLocals字段是ThreadLocalMap类型(你可以简单理解为一个key value的Map),key是ThreadLocal对象,value是我们在外层设置的值

  • 当我们调用threadLocal.set(value)方法的时候,会找到当前线程的threadLocals这个map,然后以this作为key去set key value
  • 当我们调用threadLocal.get()方法的时候,会找到当前线程的threadLocals这个map,然后以this作为key去get value
  • 当我们调用threadLocal.remove()方法的时候,会找到当前线程的threadLocals这个map,然后以this作为key去remove

这就相当于:

Thread.currentThread().threadLocals.set(threadLocal1, "value1");
.....
//value的值是value1
String value = Thread.currentThread().threadLocals.get(threadLocal1);

因为每个Thread都是不同的对象,所以他们的threadLocals也是不同的map,threadLocal在不同的线程里工作时,实际上是从不同的map里get/set,这也就是线程安全的原因了,了解到这一点就差不多了。

再深入一些,ThreadLocalMap的结构

如果继续翻ThreadLocalMap的源码,会发现它有个字段table,是Entry类型的数组。

我们不妨写段代码,把ThreadLocalMap的结构输出出来。

由于Thread.threadLocals和ThreadLocalMap类不是public的,我们只有通过反射来获取它的值。反射的代码如下(如果嫌长可以不看,直接看输出):

static Object getThreadLocalMap(Thread thread) throws NoSuchFieldException, IllegalAccessException {
//get thread.threadLocals
Field threadLocals = Thread.class.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
return threadLocals.get(thread);
} static void printThreadLocalMap(Object threadLocalMap) throws NoSuchFieldException, IllegalAccessException {
String threadName = Thread.currentThread().getName(); if(threadLocalMap == null){
Utils.println("threadMap is null, threadName:" + threadName);
return;
} Utils.println(threadName); //get threadLocalMap.table
Field tableField = threadLocalMap.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[])tableField.get(threadLocalMap);
Utils.println("----threadLocals (ThreadLocalMap), table.length = " + table.length); for (int i = 0; i < table.length; i ++){
WeakReference<ThreadLocal<?>> entry = (WeakReference<ThreadLocal<?>>)table[i];
printEntry(entry, i);
}
}
static void printEntry(WeakReference<ThreadLocal<?>> entry, int i) throws NoSuchFieldException, IllegalAccessException {
if(entry == null){
Utils.println("--------table[" + i + "] -> null");
return;
}
ThreadLocal key = entry.get();
//get entry.value
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry); Utils.println("--------table[" + i + "] -> entry key = " + key + ", value = " + value);
}

测试代码:

static void testStructure() throws InterruptedException {
Utils.println("-------------testStructure----------------");
ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>(); Thread thread1 = new Thread(() -> {
threadLocal1.set("threadLocal1-value");
threadLocal2.set("threadLocal2-value"); try {
Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
printThreadLocalMap(threadLocalMap); } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} }, "thread1"); thread1.start(); //wait thread1 done
thread1.join(); Thread thread2 = new Thread(() -> {
threadLocal1.set("threadLocal1-value");
try {
Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
printThreadLocalMap(threadLocalMap); } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} }, "thread2"); thread2.start();
thread2.join();
Utils.println("------------------------------------------");
}

我们在创建了两个ThreadLocal的对象threadLocal1和threadLocal2,在线程1里为这两个对象设置值,在线程2里只为threadLocal1设置值。然后分别打印出这两个线程的threadLocalMap。

输出结果为:

-------------testStructure----------------
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@33baa315, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
thread2
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

从结果上可以看出:

  • 线程1和线程2的threadLocalMap对象的table字段,是个数组,长度都是16
  • 由于线程1里给两个threadLocal对象设置了值,所以线程1的ThreadLocalMap里有两个entry,数组下标分别是1和10,其余的是null(如果你自己写代码验证,下标不一定是1和10,不需要纠结这个问题,只要前后对的上就行)
  • 由于线程2里只给一个threadLocal对象设置了值,所以线程1的ThreadLocalMap里只有一个entry,数组下标是10,其余的是null
  • threadLocal1这个对象在两个线程里都设置了值,所以当它作为key加入二者的threadLocalMap时,key是一样的,都是java.lang.ThreadLocal@4d42db5c;下标也是一样的,都是10。

为什么是WeakReference

查看Entry的源码,会发现Entry继承自WeakReference:

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

构造函数里把key传给了super,也就是说,ThreadLocalMap中对key的引用,是WeakReference的。

Weak reference objects, which do not prevent their referents from being

made finalizable, finalized, and then reclaimed. Weak references are most

often used to implement canonicalizing mappings.

通俗点解释:

当一个对象仅仅被weak reference(弱引用), 而没有任何其他strong reference(强引用)的时候, 不论当前的内存空间是否足够,当GC运行的时候, 这个对象就会被回收。

看不明白没关系,还是写代码测试一下什么是WeakReference吧...

static void testWeakReference(){
Object obj1 = new Object();
Object obj2 = new Object();
WeakReference<Object> obj1WeakRef = new WeakReference<>(obj1);
WeakReference<Object> obj2WeakRf = new WeakReference<>(obj2);
//obj32StrongRef是强引用
Object obj2StrongRef = obj2;
Utils.println("before gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef); //把obj1和obj2设为null
obj1 = null;
obj2 = null;
//强制gc
forceGC(); Utils.println("after gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);
}

结果输出:

before gc: obj1WeakRef = java.lang.Object@4554617c, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482
after gc: obj1WeakRef = null, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482

从结果上可以看出:

  • 我们先new了两个对象(为避免混淆,称他们为Object1和Object2),分别用变量obj1和obj2指向它们,同时定义了一个obj2StrongRef,也指向Object2,最后把obj1和obj2均指向null
  • 由于Object1没有变量强引用它了,所以在gc后,Object1被回收了,obj1WeakRef.get()返回了null
  • 由于Object2还有obj2StrongRef在引用它,所以gc后,Object2依然存在,没有被回收。

那么,ThreadLocalMap中对key的引用,为什么是WeakReference的呢?

因为大部分情况下,线程不死

大部分情况下,线程不会频繁的创建和销毁,一般都会用线程池。所以线程对象一般不会被清除,线程的threadLocalMap就一直存在。

如果key对ThreadLocal是强引用,那么key永远不会被回收,即使我们程序里再也不用它了。

但是key是弱引用的话,情况就会得到改善:只要没有指向threadLocal的强引用了,这个ThreadLocal对象就会被清理。

我们还是写代码测试一下吧。

/**
* 测试ThreadLocal对象什么时候被回收
* @throws InterruptedException
*/
static void testGC() throws InterruptedException {
Utils.println("-----------------testGC-------------------");
Thread thread1 = new Thread(() -> {
ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>(); threadLocal1.set("threadLocal1-value");
threadLocal2.set("threadLocal2-value"); try {
Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
Utils.println("print threadLocalMap before gc");
printThreadLocalMap(threadLocalMap); //set threadLocal1 unreachable
threadLocal1 = null; forceGC(); Utils.println("print threadLocalMap after gc");
printThreadLocalMap(threadLocalMap); } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} }, "thread1"); thread1.start();
thread1.join();
Utils.println("------------------------------------------");
}

我们在一个线程里为两个ThreadLocal对象赋值,最后把其中一个对象的强引用移除,gc后打印当前线程的threadLocalMap。

输出结果如下:

-----------------testGC-------------------
print threadLocalMap before gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@56342d38, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
------------------------------------------

从输出结果可以看到,当我们把threadLocal1的强引用移除并gc之后,table[10]的key变成了null,说明threadLocal1这个对象被回收了;threadLocal2的强引用还在,所以table[1]的key不是null,没有被回收。

但是你发现没有,table[10]的key虽然是null了,但value还活着! table[10]这个entry对象,也活着!

是的,因为只有key是WeakReference....

无用的entry什么时候被回收?

通过查看ThreadLocal的源码,发现在ThreadLocal对象的get/set/remove方法执行时,都有机会清除掉map中已经无用的entry。

最容易验证清除无用entry的场景分别是:

  • remove:这个不用说了,这哥们本来就是做这个的
  • get:当一个新的threadLocal对象(没有set过value)发生get调用时,也会作为新的entry加入map,在加入的过程中,有机会清除掉无用的entry,逻辑和下面的set相同。
  • set: 当一个新的threadLocal对象(没有set过value)发生set调用时,会在map中加入新的entry,此时有机会清除掉无用的entry,清除的逻辑是:
    • 清除掉table数组中的那些无用entry中的一部分,记住是一部分,这个一部分可能全部,也可能是0,具体算法请看ThreadLocalMap.cleanSomeSlots,这里不解释了。
    • 如果上一步的"一部分"是0(即清除了0个),并且map的size(是真实size,不是table.length)大于等于threshold(table.length的2/3),会执行一次rehash,在rehash的过程中,清理掉所有无用的entry,并减小size,清理后的size如果还大于等于threshold - threshold/4,则把table扩容为原来的两倍大小。

还有其他场景,但不好验证,这里就不提了。

ThreadLocal源码就不贴了,贴了也讲不明白,相关逻辑在setInitialValue、cleanSomeSlots、expungeStaleEntries、rehash、resize等方法里。

在我们写代码验证entry回收逻辑之前,还需要简单的提一下ThreadLocalMap的hash算法。

entry数组的下标如何确定?

每个ThreadLocal对象,都有一个threadLocalHashCode变量,在加入ThreadLocalMap的时候,根据这个threadLocalHashCode的值,对entry数组的长度取余(hash & (len - 1)),余数作为下标。

那么threadLocalHashCode是怎么计算的呢?看源码:

public class ThreadLocal<T>{
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
...
}

ThreadLocal类维护了一个全局静态字段nextHashCode,每new一个ThreadLocal对象,nextHashCode都会递增0x61c88647,作为下一个ThreadLocal对象的threadLocalHashCode。

这个0x61c88647,是个神奇的数字,只要以它为递增值,那么和2的N次方取余时,在有限的次数内不会发生重复。

比如和16取余,那么在16次递增内,不会发生重复。还是写代码验证一下吧。

int hashCode = 0;
int HASH_INCREMENT = 0x61c88647;
int length = 16; for(int i = 0; i < length ; i ++){
int h = hashCode & (length - 1);
hashCode += HASH_INCREMENT;
System.out.println("h = " + h + ", i = " + i);
}

输出结果为:

h = 0, i = 0
h = 7, i = 1
h = 14, i = 2
h = 5, i = 3
h = 12, i = 4
h = 3, i = 5
h = 10, i = 6
h = 1, i = 7
h = 8, i = 8
h = 15, i = 9
h = 6, i = 10
h = 13, i = 11
h = 4, i = 12
h = 11, i = 13
h = 2, i = 14
h = 9, i = 15

你看,h的值在16次递增内,没有发生重复。 但是要记住,2的N次方作为长度才会有这个效果,这也解释了为什么ThreadLocalMap的entry数组初始长度是16,每次都是2倍的扩容。

验证新threadLocal的get和set时回收部分无效的entry

为了验证出结果,我们需要先给ThreadLocal的nextHashCode重置一个初始值,这样在测试的时候,每个threadLocal的数组下标才会按照我们设计的思路走。

static void resetNextHashCode() throws NoSuchFieldException, IllegalAccessException {
Field nextHashCodeField = ThreadLocal.class.getDeclaredField("nextHashCode");
nextHashCodeField.setAccessible(true);
nextHashCodeField.set(null, new AtomicInteger(1253254570));
}

然后在测试代码里,我们先调用resetNextHashCode方法,然后加两个ThreadLocal对象并set值,gc前把强引用去除,gc后再new两个新的theadLocal对象,分别调用他们的get和set方法。

在每个关键点打印出threadLocalMap做比较。

static void testExpungeSomeEntriesWhenGetOrSet() throws InterruptedException {
Utils.println("----------testExpungeStaleEntries----------");
Thread thread1 = new Thread(() -> {
try {
resetNextHashCode(); //注意,这里必须有两个ThreadLocal,才能验证出threadLocal1被清理
ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>(); threadLocal1.set("threadLocal1-value");
threadLocal2.set("threadLocal2-value"); Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
//set threadLocal1 unreachable
threadLocal1 = null;
threadLocal2 = null;
forceGC(); Utils.println("print threadLocalMap after gc");
printThreadLocalMap(threadLocalMap); ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
newThreadLocal1.get();
Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
printThreadLocalMap(threadLocalMap); ThreadLocal<String> newThreadLocal2 = new ThreadLocal<>();
newThreadLocal2.set("newThreadLocal2-value");
Utils.println("print threadLocalMap after call a new newThreadLocal2.set");
printThreadLocalMap(threadLocalMap); } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} }, "thread1"); thread1.start();
thread1.join();
Utils.println("------------------------------------------");
}

程序输出结果为:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> null
--------table[9] -> null
--------table[10] -> entry key = null, value = threadLocal1-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal2-value
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> null
print threadLocalMap after call a new newThreadLocal2.set
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> null
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
--------table[9] -> null
--------table[10] -> null
--------table[11] -> null
--------table[12] -> null
--------table[13] -> null
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@2e93c547, value = newThreadLocal2-value
------------------------------------------

从结果上来看,

  • gc后table[1]和table[10]的key变成了null
  • new newThreadLocal1.get后,新增了table[8],table[10]被清理了,但table[1]还在(这就是cleanSomeSlots中some的意思)
  • new newThreadLocal2.set后,新增了table[15],table[1]被清理了。

验证map的size大于等于table.length的2/3时回收所有无效的entry

	static void testExpungeAllEntries() throws InterruptedException {
Utils.println("----------testExpungeStaleEntries----------");
Thread thread1 = new Thread(() -> {
try {
resetNextHashCode(); int threshold = 16 * 2 / 3;
ThreadLocal[] threadLocals = new ThreadLocal[threshold - 1];
for(int i = 0; i < threshold - 1; i ++){
threadLocals[i] = new ThreadLocal<String>();
threadLocals[i].set("threadLocal" + i + "-value");
} Object threadLocalMap = getThreadLocalMap(Thread.currentThread()); threadLocals[1] = null;
threadLocals[8] = null;
//threadLocals[6] = null;
//threadLocals[4] = null;
//threadLocals[2] = null;
forceGC(); Utils.println("print threadLocalMap after gc");
printThreadLocalMap(threadLocalMap); ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
newThreadLocal1.set("newThreadLocal1-value");
Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
printThreadLocalMap(threadLocalMap); } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
} }, "thread1"); thread1.start();
thread1.join();
Utils.println("------------------------------------------");
}

我们先创建了9个threadLocal对象并设置了值,然后去掉了其中2个的强引用(注意这2个可不是随意挑选的)。

gc后再添加一个新的threadLocal,最后打印出最新的map。输出为:

----------testExpungeStaleEntries----------
print threadLocalMap after gc
thread1
----threadLocals (ThreadLocalMap), table.length = 16
--------table[0] -> null
--------table[1] -> entry key = null, value = threadLocal1-value
--------table[2] -> entry key = null, value = threadLocal8-value
--------table[3] -> null
--------table[4] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[9] -> null
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
print threadLocalMap after call a new newThreadLocal1.get
thread1
----threadLocals (ThreadLocalMap), table.length = 32
--------table[0] -> null
--------table[1] -> null
--------table[2] -> null
--------table[3] -> null
--------table[4] -> null
--------table[5] -> null
--------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
--------table[7] -> null
--------table[8] -> null
--------table[9] -> entry key = java.lang.ThreadLocal@1dae16b1, value = newThreadLocal1-value
--------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
--------table[11] -> null
--------table[12] -> null
--------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
--------table[14] -> null
--------table[15] -> null
--------table[16] -> null
--------table[17] -> null
--------table[18] -> null
--------table[19] -> null
--------table[20] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
--------table[21] -> null
--------table[22] -> null
--------table[23] -> null
--------table[24] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
--------table[25] -> null
--------table[26] -> null
--------table[27] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
--------table[28] -> null
--------table[29] -> null
--------table[30] -> null
--------table[31] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
------------------------------------------

从结果上看:

  • gc后table[1]和table[2](即threadLocal1和threadLocal8)的key变成了null
  • 加入新的threadLocal后,table的长度从16变成了32(因为此时的size是8,正好等于10 - 10/4,所以扩容),并且threadLocal1和threadLocal8这两个entry不见了。

如果在gc前,我们把threadLocals[1、8、6、4、2]都去掉强引用,加入新threadLocal后会发现1、8、6、4、2被清除了,但没有扩容,因为此时size是5,小于10-10/4。这个逻辑就不贴测试结果了,你可以取消注释上面代码中相关的逻辑试试。

大部分场景下,ThreadLocal对象的生命周期是和app一致的,弱引用形同虚设

回到现实中。

我们用ThreadLocal的目的,无非是在跨方法调用时更方便的线程安全地存储和使用变量。这就意味着ThreadLocal的生命周期很长,甚至和app是一起存活的,强引用一直在。

既然强引用一直存在,那么弱引用就形同虚设了。

所以在确定不再需要ThreadLocal中的值的情况下,还是老老实实的调用remove方法吧!

代码地址

https://github.com/kongxiangxin/pine/tree/master/threadlocal

ThreadLocal原理分析与代码验证的更多相关文章

  1. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  2. ThreadLocal原理分析

    本文结构 ThreadLocal简介 (简要说明ThreadLocal的作用) ThreadLocal实现原理(说明ThreadLocal的常用方法和原理) ThreadLocalMap的实现 (说明 ...

  3. Logistic回归分类算法原理分析与代码实现

    前言 本文将介绍机器学习分类算法中的Logistic回归分类算法并给出伪代码,Python代码实现. (说明:从本文开始,将接触到最优化算法相关的学习.旨在将这些最优化的算法用于训练出一个非线性的函数 ...

  4. ThreadLocal原理分析与使用场景

    什么是ThreadLocal变量 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.这里有几点需要注意: 因为每个 Thr ...

  5. 第七篇:Logistic回归分类算法原理分析与代码实现

    前言 本文将介绍机器学习分类算法中的Logistic回归分类算法并给出伪代码,Python代码实现. (说明:从本文开始,将接触到最优化算法相关的学习.旨在将这些最优化的算法用于训练出一个非线性的函数 ...

  6. IAT Hook 原理分析与代码编写

    Ring 3层的 IAT HOOK 和 EAT HOOK 其原理是通过替换IAT表中函数的原始地址从而实现Hook的,与普通的 InlineHook 不太一样 IAT Hook 需要充分理解PE文件的 ...

  7. Apriori 关联分析算法原理分析与代码实现

    前言 想必大家都听过数据挖掘领域那个经典的故事 - "啤酒与尿布" 的故事. 那么,具体是怎么从海量销售信息中挖掘出啤酒和尿布之间的关系呢? 这就是关联分析所要完成的任务了. 本文 ...

  8. 第一篇:K-近邻分类算法原理分析与代码实现

    前言 本文介绍机器学习分类算法中的K-近邻算法并给出伪代码与Python代码实现. 算法原理 首先获取训练集中与目标对象距离最近的k个对象,然后再获取这k个对象的分类标签,求出其中出现频数最大的标签. ...

  9. 第十四篇:Apriori 关联分析算法原理分析与代码实现

    前言 想必大家都听过数据挖掘领域那个经典的故事 - "啤酒与尿布" 的故事. 那么,具体是怎么从海量销售信息中挖掘出啤酒和尿布之间的关系呢? 这就是关联分析所要完成的任务了. 本文 ...

随机推荐

  1. 你不知道的Canvas(一)

    Canvas基础 一.Canvas是什么 Canvas是一个可以使用脚本(通常为JavaScript来绘制图形的HTML) 元素.例如,它可以用于绘制图表.制作图片构图或者制作简单的动画,主要用来绘制 ...

  2. Open vSwitch系列之一 Open vSwitch诞生

    Open vSwitch系列之一 Open vSwitch诞生 Open vSwitch系列之二 安装指定版本ovs 2006年,SDN诞生于美国GENI项目资助的斯坦福大学Clean Slate课题 ...

  3. 【python数据分析实战】电影票房数据分析(一)数据采集

    目录 1.获取url 2.开始采集 3.存入mysql 本文是爬虫及可视化的练习项目,目标是爬取猫眼票房的全部数据并做可视化分析. 1.获取url 我们先打开猫眼票房http://piaofang.m ...

  4. 06_基本框架_VMCS_GuestArea

    1 Guest-State Area 1.1 References 注释: 其实 内容很多 一般是不背 的:需要的时候来查就行:但是要大致知道是做啥的. 代码 > 设置 Guest Area / ...

  5. 百万年薪python之路 -- 并发编程之 协程

    协程 一. 协程的引入 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两 ...

  6. 选择器, ,>,+,~

    一.后代选择器 选取指定元素的后代元素 与子元素选择器相比,后代选择器选取的不一定是直接后代(儿子),而是作用于所有后代元素(儿子.孙子.重孙…)都可以. 二.(>)子元素选择器 选取某个元素的 ...

  7. ansible之roles

    基于之前的博客介绍,我们已经了解了 tasks 和 handlers,那怎样组织 playbook 才是最好的方式呢?简单的回答就是:使用 roles ! Roles 基于一个已知的文件结构,去自动的 ...

  8. ansible剧本之playbook操作

    ansible 剧本 yaml介绍: 是一个编程语言 文件后缀名 yaml yml 数据对应格式: 字典: key: value 列表: [] - ansible-playbook命令格式 执行顺序: ...

  9. JavaScript中instanceof的判断依据

    读jquery源码的时候,发现作者为了让创建jquery对象更容易使用了用函数返回对象的方法 jQuery = function( selector, context ) { return new j ...

  10. c# 保留两位小数点

    保留两位小数点 由于简单的原因大家直接看代码块. using System; namespace HelloWorld { class Program { static void Main(strin ...