ThreadLocal

目录

场景:

 实现:

那么ThreadLocalMap又是啥呢?

ThreadLocalMap的底层结构

那么ThreadLocalMap为什么要用数组呢?

那么set对象存放在哪里?

如何共享ThreadLocal数据,让多个线程访问

内存泄露问题

ThreadLocal练习


目录

场景:

 实现:

那么ThreadLocalMap又是啥呢?

ThreadLocalMap的底层结构

那么ThreadLocalMap为什么要用数组呢?

那么set对象存放在哪里?

如何共享ThreadLocal数据,让多个线程访问

内存泄露问题

ThreadLocal练习


前置:

 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又是啥呢?

从源码可知,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;
  
     ……

ThreadLocalMap的底层结构

类似于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维护的是一个哈希表一样的结构,本质也就是数组

那么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;

那么set对象存放在哪里?

(22条消息) JVM-01(阶段性学习)_Fairy要carry的博客-CSDN博客

因为是线程,众所周知,每个线程都会有自己的栈内存,,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

注:当然我们new 的ThreadLocal实例虽然被Thread给引用,进行了一个持有的操作,但是new嘛,所以也是位于堆上的;

如何共享ThreadLocal数据,让多个线程访问

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

内存泄露问题

2024更新

实际上,ThreadLocalMap 使用了弱引用来避免内存泄漏。当一个线程结束时,其对应的 ThreadLocalMap 会被垃圾回收,进而清除所有的 Entry 对象。由于 Entry 对象使用的是弱引用,如果该对象在其他地方没有被引用,那么它会在垃圾回收过程中被释放掉。这样就可以避免 ThreadLocal 导致的内存泄漏问题。

---------------------------------------------------------------------------------------------------------------------

我们首先回顾一下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练习

        //主线程中
		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());

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fairy要carry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值