目录
前言
一、ThreadLocal 分析
二、ThreadLocal的基本使用
三、实现原理
四、存在问题
1.引用
2.问题详情
3.解决方案
五、章末
前言
小伙伴们大家好,谈到多线程就不可不谈解决线程安全 的操作类ThreadLocal了
一、ThreadLocal 分析
ThreadLocal会为每个线程分配独立的线程副本,解决并发访问冲突的问题,并且实现线程内的资源共享。
举个例子,刚开始学Java的时候都少不了手动连接数据库吧,创建连接,填写用户名密码等(霍 死去的物种连接数据库库方式开始攻击我)
回归正题, JDBC 操作数据库不可或缺的工具,底层就是将线程的Connection放入各自的ThreadLocal当中,保证了每个线程都在自己的Connection上操作数据库,避免了误操作;
二、ThreadLocal的基本使用
常见的方法有三个,set(),get(),remove(),以下案例是具体使用
public class ThreadLocalTest {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
String name = Thread.currentThread().getName();
threadLocal.set("test_1");
printThread(name);
System.out.println(name + " after remove :"+threadLocal.get());
},"t1").start();
new Thread(()->{
String name = Thread.currentThread().getName();
threadLocal.set("test_2");
printThread(name);
System.out.println(name + " after remove :"+threadLocal.get());
},"t2").start();
}
static void printThread(String str){
//打印当前线程本地变量的值
System.out.println(str + " :"+threadLocal.get());
//清除本地变量的值
threadLocal.remove();
}
}
分析
新建一个测试类,实例化一个threadLocal对象,泛型表示只能赋值字符串变量,main方法内新建两个线程,线程的操作是一样的,先获取当前线程的名称,threadLocal给本地变量赋值,打印并清除线程的本地变量的值,之后再次打印;
三、实现原理
点击进入set(),里面用到了createMap(),再次点击,实例化了ThreadLocalMap对象,再次点击,终于到了底层(初步的底层)创建了一个table数组,这里才是真正存储数据的位置;(说句题外话,疯狂套娃呢搁着(bushi),不过挺有意思的,就像每次都发现新大陆一样)
四、存在问题
1.引用
万物不可能十全十美,ThreadLocal对象也一样,伴随着内存泄漏问题 ,先科普一波java对象的四种引用
强引用(Strong Reference):在程序中正常使用的对象引用都是强引用。如果一个对象被一个强引用所引用,那么这个对象就不会被回收,即使内存空间不足时也不会被回收。当强引用指向的对象不再被使用,或者被赋值为
null
时,垃圾回收器才会回收该对象。软引用(Soft Reference):软引用用于描述还有用但并非必需的对象。软引用关联的对象只有在内存不足时才会被回收。当垃圾回收器开始回收内存时,软引用所引用的对象会被保留直至内存确实不足时才会被回收。
弱引用(Weak Reference):弱引用用于描述非必需对象,它比软引用更加弱化。被弱引用关联的对象只有在垃圾回收器运行时才会被回收。垃圾回收器在处理弱引用时,不论内存是否充足,都会回收它所引用的对象。
虚引用(Phantom Reference):虚引用也称为幽灵引用,它是最弱的一种引用类型。虚引用关联的对象完全没有被引用,只有当其所关联的对象被垃圾回收器回收时才会被加入到引用队列中。虚引用的作用是在对象被销毁之前给出一个通知,可以在对象被销毁后进行一些清理操作。
什么?不想咬文嚼字?伪代码来了
- 强引用:User user = new User(); 这种方式就是强引用
- 弱引用:WeakReference weak = new WeakReference(user);这种就是弱引用了
2.问题详情
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。 这就导致了⼀个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程⼀直持续运行,那么这个Entry对象中的value就有可能⼀直得不到回收,发⽣内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
3.解决方案
方法是有的,因为按照道理⼀个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了,所以在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了 ,like this, 务必手动remove();
ThreadLocal<String> threadLocal = new ThreadLocal();
try{
threadLocal.set("AA");
...业务逻辑
} finally{
threadLocal.remove();
}
五、章末
好了,文章到这里就结束了。