ThreadLocal详解

目录

1. 概述

2. 常用方法 

3. ThreadLocalMap的内部结构

3.1 为什么用ThreadLocal做key

3.2 ThreadLocalMap如何查找数据

4. 父子线程如何共享数据

5. ThreadLocal如何避免内存泄露

6. ThreadLocal应用场景


1. 概述

ThreaLocal是线程局部变量,用于保存线程中的数据,threadlocal中保存的数据仅属于当前线程,对于其他线程来说是隔离的,不同的线程之间不会相互干扰。

ThreadLocal利用Thread中的ThreadLocalMap来进行数据存储。

image.png

2. 常用方法 

\bullet public void set(T value):存储数据至当前线程的ThreadLocalMap

    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。 使用ThreadLocal对象做key,将数据保存至当前线程的ThreadLocalMap的value中。

\bullet public T get():从当前线程的ThreadLocalMap中获取数据

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

方法实现:获取当前线程的ThreadLocalMap,使用ThreadLocal对象做key,获取数据(Entry类型),若Entry不为空,则返回Entry的value值。

\bullet public void remove():从当前线程的ThreadLocalMap中删除数据

在线程池的线程复用场景中,线程执行完毕时一定要调用remove(),避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。

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

方法实现:获取当前线程的ThreadLocalMap,若获取的mapbuweikong 则使用当前ThreadLocal对象做key,删除数据

3. ThreadLocalMap的内部结构

ThreadLocalMap内部数据结构是一个Entry类型的数组。每个Entry对象的key为ThreadLocal实例本身,value为实际存储的数据。

640.png

3.1 为什么用ThreadLocal做key

 如果在应用中,一个线程中只使用了一个ThreadLocal对象,那么使用Thread(当前线程)做key是没有问题的,代表每个Thread线程对应一个value。但是如果一个线程中有多个ThreadLocal对象这时候在使用Thread做key就会产生混淆。

640.png

所以应该以 ThreadLocal对象做Key,这样才能通过具体ThreadLocal对象的get()方法,获取到当前线程的ThreadLocalMap,然后进一步获取到对应的Entry。

3.2 ThreadLocalMap如何查找数据

使用ThreadLocal对象做key,在当前线程的ThreadLocalMap中获取对应的value值。

ThreadLocalMap集合的底层数据结构使用Entry[]数组保存Key-Value键值对数据。所以,当通过ThreadLocal的get、set()、remove()等方法,访问ThreadLocalMap时,最终都会通过一个下标,来完成对数组中的元素访问。

下标的计算就是用当前的Key的hashcode值与当前数组长度减1做“按位与”运算(&),这种计算相当于用当前的Key的hashcode值跟当前数组长度减1做“取余”运算(%),但是“&按位与”运算的效率更高。

4. 父子线程如何共享数据

在有些业务场景中,有可能需要在父子线程中共享数据的。而ThreadLocal能不能做到呢,请看如下代码

public class Demo03 {
	public static ThreadLocal<String> local = new ThreadLocal<String>();
	
	public static void main(String[] args) {
		local.set("迈巴赫");
		System.out.println(local.get());
		//创建子线程
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				//获取local中的值
				System.out.println(local.get());
			}
		});
		t1.start();
		
	}

}
运行结果:
迈巴赫
null

从运行结果可以看输出,ThreadLocal是行不通的。主线程和子线程两个线程对象,各自拥有不同的ThreadLocalMap。应该使用InheritableThreadLocal,它是JDK自带的类,继承了ThreadLocal类。InheritableThreadLocal类中的get(),set()方法都是继承父类方法。

public class Demo03 {
	public static ThreadLocal<String> local = new InheritableThreadLocal<String>();
	public static void main(String[] args) {
		local.set("迈巴赫");
		System.out.println(local.get());
		//创建子线程
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				//获取local中的值
				System.out.println(local.get());
			}
		});
		t1.start();
		
	}

}
运行结果:
迈巴赫
迈巴赫

5. ThreadLocal如何避免内存泄露

内存泄漏指的是程序中存在某些对象或资源长期滞留在内存,没有得到回收。随着时间的推移,这些未释放的对象或资源会越来越多,最终耗尽系统的内存资源,导致系统崩溃。

在ThreadLocal中,避免内存泄漏最主要的方法就是,使用完毕后,在finally调用ThreadLocal对象的remove()方法。放在finally块中是当如果业务代码出现异常时,也能及时清理没用的数据。

remove()方法中会把Entry中的key和value都设置成null,这样就能被GC及时回收,无需触发额外的清理机制,所以它能解决内存泄露问题。

6. ThreadLocal应用场景

\bullet 线程数据隔离

保证线程各自的数据安全,实现线程数据彼此之间隔离。这样操作,可以在多线程环境下,可以防止当前线程的数据被其他线程修改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。

\bullet 跨函数传递

通常情况下,我们会在每个调用的方法上加上需要传递的参数。但是如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可,这样可以降低这些类或者方法之间的耦合度。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值