目录
前置:
ThreadLocal——线程本地存储。也就是线程本地变量的管理者,一般用于实现线程隔离(数据隔离),在线程中使用它存储数据,仅在当前线程访问使用
其实,它仅仅是一个管理者,真正存储数据的不是它,而是一个叫做ThreadLocalMap的对象,每个线程都有一个自己的TreadLocalMap对象(在Thread中对象名为:threadLocals或者inheritableThreadLocals)。
比如JDBC,我们每个线程进行数据库操作,连接都有自己独有的,而不会出现a连接到b上面

这里我们会体现他是怎么做到线程隔离的
- ThreadLocal<String> localName = new ThreadLocal();
- localName.set("张三");
- String name = localName.get();
- localName.remove();
set源代码
会发现,设置值->会先获取当前线程,根据当前线程得到它的ThreadLocalMap,然后通过ThreadLocalMap赋值
从源码可知,ThreadLocalMap其实就是Thread中的一个叫threadLocals的变量获取的
所以说,数据的逻辑是在Thread当前线程中,而不是在ThreadLocal,它相当于只是一个管理类
- public void set(T value) {
- Thread t = Thread.currentThread();// 获取当前线程
- ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
- if (map != null) // 校验对象是否为空
- map.set(this, value); // 不为空set
- else
- createMap(t, value); // 为空创建一个map对象
- }
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
可以发现每个Thread都维护自己的ThreadLocals变量,当每个线程创建ThreadLocal时候,实际上数据是存在ThreadLocals里面的,从而实现隔离,所以说数据隔离的逻辑在Thread中;
- public class Thread implements Runnable {
- ……
-
- /* 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;
-
- ……
类似于HashMap,其实不然,源码可知是一个Entry->就是一个键值对对象,继承WeakReference弱引用,key是ThreadLocal的引用,但是存储数据的逻辑是给到Thread本身,就像在公司做事一样,我现在在这个公司做事,我是Thread,事情是value,在的地方是ThreadLocal
(22条消息) Java中Map集合中的Entry对象_宇智波爱编程的博客-CSDN博客_entry

- static class ThreadLocalMap {
-
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
-
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- ……
- }
其实可以发现,本身ThreadLocalMap维护的是一个哈希表一样的结构,本质也就是数组
之前我们说过,ThreadLocal就像是维护了一个地方,存储值的逻辑还是在Thread本身,将值存在ThreadLocalMap中,而ThreadLocalMap维护的是一个哈希表,每个Entry都是一个键值对,键就是在哪做事(ThreadLocal),对就是值,所以说我们Thread是可以做很多事的,做的地方也可以不一样(可以创建多个ThreadLocal对象),只是说逻辑都在当前Thread本身上,所以肯定用数组来存;
场景:开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里;
- static class ThreadLocalMap {
-
-
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
-
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
-
-
- private static final int INITIAL_CAPACITY = 16;
-
-
- private Entry[] table;
(22条消息) JVM-01(阶段性学习)_Fairy要carry的博客-CSDN博客
因为是线程,众所周知,每个线程都会有自己的栈内存,,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。
注:当然我们new 的ThreadLocal实例虽然被Thread给引用,进行了一个持有的操作,但是new嘛,所以也是位于堆上的;
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。
- private void test() {
- final ThreadLocal threadLocal = new InheritableThreadLocal();
- threadLocal.set("帅得一匹");
- Thread t = new Thread() {
- @Override
- public void run() {
- super.run();
- Log.i( "张三帅么 =" + threadLocal.get());
- }
- };
- t.start();
- }
我们首先回顾一下ThreadLocalMap,里面的Entry继承了WeakReference弱引用
说明ThreadLocal在保存数据时会吧自己当做key存在ThreadLocalMap中,也就是说ThreadLocal会被ThreadLocalMap持有引用(也就是Thread当前线程),正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了

(22条消息) 垃圾回收流程-简单叙述_Fairy要carry的博客-CSDN博客_垃圾回收过程
导致问题:
ThreadLocal因为在Thread本身逻辑里面是没有被强引用的,所以发生GC时候会被回收,但是value会暴露出来
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露;
如何解决:
最后remove即可,就可以把值给清空了
- ThreadLocal<String> localName = new ThreadLocal();
- try {
- localName.set("张三");
- ……
- } finally {
- localName.remove();
- }
remove逻辑:把所有对应的值都置空
- 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.get() == key) {
- e.clear();
- expungeStaleEntry(i);
- return;
- }
- }
- }
那么问题来了,为什么要把ThreadLocalMap的key设计成弱引用?
key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。
- //主线程中
- ThreadLocal<String> k1 = new ThreadLocal<>();
- ThreadLocal<Integer> k2 = new ThreadLocal<>();
- ThreadLocal<Object> k3 = new ThreadLocal<>();
- k1.set("main thread");
- k2.set(1000);
- k3.set("hallo~");
-
- Thread thread1 = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("thread1:"+k1.get());
- System.out.println("thread1:"+k2.get());
- System.out.println("thread1:"+k3.get());
-
- k1.set("123");
- k2.set(1);
- k3.set("sss");
-
- System.out.println("thread1:"+k1.get());
- System.out.println("thread1:"+k2.get());
- System.out.println("thread1:"+k3.get());
- System.out.println();
- }
- });
-
- Thread thread2 = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- System.out.println("thread2:"+k1.get());
- System.out.println("thread2:"+k2.get());
- System.out.println("thread2:"+k3.get());
-
- k1.set("abc");
- k2.set(2);
- k3.set(k3);
-
- System.out.println("thread2:"+k1.get());
- System.out.println("thread2:"+k2.get());
- System.out.println("thread2:"+k3.get());
- System.out.println();
- }
- });
-
- Thread thread3 = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- System.out.println("thread3:"+k1.get());
- System.out.println("thread3:"+k2.get());
- System.out.println("thread3:"+k3.get());
-
- k1.set("haha");
- k2.set(10);
- k3.set(new Object());
-
- System.out.println("thread3:"+k1.get());
- System.out.println("thread3:"+k2.get());
- System.out.println("thread3:"+k3.get());
- System.out.println();
- }
- });
-
- thread1.start();
- thread2.start();
- thread3.start();
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("main:"+k1.get());
- System.out.println("main:"+k2.get());
- System.out.println("main:"+k3.get());