目录
1 ThreadLocal介绍
官话:ThreadLocal用来提供线程内的局部变量,这种变量在多线程环境下访问(通过get和set方法)时保证各个线程的变量相对于其他线程的变量。ThreadLocal示例通常来说都是private static类型的,用来关联线程和线程上下文。
我们可以得知ThreadLocal的作用是:提供线程内的局部变量,不同线程之间不会互相干扰,这种变量在线程周期内起作用,减少同一个线程内多个函数或多个组件之间一些公共变量的复杂度。
白话:
1 线程并发:在多线程并发的场景下(单线程也能用,但是没有必要)
2 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
3 线程隔离:每个线程之间的变量都是独立的,不会相互影响
2 使用ThreadLoca解决问题小demo
大家都知道SimpleDateFormat在多线程环境下是不安全的,那么解决这个问题有许多种方案,今天利用threadlocal来解决这个问题。
公共类:
public class DateUtilNotSafe {
private static final SimpleDateFormat sdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String dataStr) {
Date date = null;
try {
date = sdt.parse(dataStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
测试类:
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(20, 20, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), threadFactory);
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(()-> System.out.println(DateUtilNotSafe.parse("2000-03-21 16:34:30")));
}
threadPoolExecutor.shutdown();
}
}
测试结果:
使用 threadlocal修改之后公共类:
public class DateUtilNotSafe {
private static final ThreadLocal<DateFormat> THREAD_LOCAL = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);
public static Date parse(String dataStr) {
Date date = null;
try {
date = THREAD_LOCAL.get().parse(dataStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
测试结果:
因为在多线程环境下,多个线程会共享这一个SimpleDateFormat对象,解决这个问题,只需要让各个线程只操作自己的对象就可以了。而threadlocal就是让线程自己独立保存自己的变量副本,每个线程都独自使用自己的变量副本,这样的话就不会影响其它的线程了
3 ThreadLocal与synchronized的区别
不同点:threadlocal从采取了以空间换时间的方式为每一个线程都提供了一份变量,多开销了一些内存,但是线程不用等待。但是synchronized则是采用了时间换空间的方式,即是加锁,让线程排队同步进行,虽然变量只有一份,但是线程需要排队进行等待,因此浪费了时间节省了空间。
但两者面向的领域是不同的,同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行同步的有效方式,threadlocal是隔离多个线程之间的数据共享,从根本上不存在多个线程资源共享变量。所以,如果需要对多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可有使用threadlocal。参考链接:https://blog.csdn.net/huangfan322/article/details/50803589
4 ThreadLocal的使用场景
1 为每个线程分配一个连接connection,这样就可以保证每个线程都在各自的连接上进行数据库操作,不会出现A线程关闭了B线程正在使用的连接
2 Session管理问题
3 SimpleDateFormat定义为全局变量线程不安全问题
5 ThreadLocal内部结构
jdk早期设计:每个ThreadLocal维护一个ThreadLocalMap,这个Map的key是Thread的本身,value是要真正存储的值。
jdk8的设计:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal的本身,value才是要真正要存储的值object。
具体过程
1)每个Thread线程内部都有一个ThreadlocalMap
2) Map中存储的位ThreadLocal对象(作为key)和线程的变量副本(value)
3)Thread内部的map是由Threadlocal来维护的,由ThreadLocal负责向map获取和设置线程的变量值(对比早起的设计是通过Thread负责向map获取和设置线程的变量值)
4)对于不同的线程,每次获取副本时,别的线程不能获取当前线程的副本值,形成了副本的隔离,互不干扰。
优点:
1 每个Map中存储的Entry数量变少
因为在系统中Threadlocal的数量肯定比Thread的数量少,所以对应 jdk8的设计比早期设计的entry数量变少。
2 当Thread销毁的时候,ThreadLocalMpa也会随之减少,减少内存的使用
6 ThreadLocal核心方法源码
public void set(T value):
总结:获取当前线程,并根据当前线程获取一个map
判断map是否存在,如果存在则将参数设置进map中
如果map为空,则给线程创建map并设置初始值
public T get() :
总结:
a:首先获取当前线程,根据当前获取一个map
b:如果获取map不为空,则在map中以ThreadLocal的引用作为key在map中获取对应的Entry e,否则转d
c:如果e不为空,则返回e,value,否则转d
d:map为空或e为空,则通过初始化方法获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的map
public void remove() :
7 使用ThreadLocal为什么会存在内存泄漏
1)内存泄漏相关概念
Memory overflow内存溢出,没有足够的内存提供申请者使用
Menory leak:内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果,内存泄漏的堆积享会导致内存溢出。
2)首先内存泄漏和Entry中使用了弱应用的key没有关系,因为不论你是用强引用还是弱引用,它都有可能产生内存泄漏
3)如果我们业务代码中,使用完了threadlocal,那么threadlocal这个引用会被回收掉。所以此时没有任何强引用指向我们的ThreadLocal,那么它将会被垃圾回收器回收,那么我们entry的key即为null了,但是我们的entry我们remove掉,而我们当前的线程依然在运行,那么我们当前线程的map依然会指向entry(虽然key为null,但是value不为null),这将会导致我们的value由于key为null将永远不会被访问到。这就导致了内存泄漏
4)ThreadLocal内存泄漏的根源:由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应的key就会导致内存泄漏。
以上只是部分内容,为了维护方便,本文已迁移到新地址:ThreadLocal初步解析 – 编程屋