ThreadLocal,并发编程
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。ThreadLocal中有四个方法:
1、get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
2、set()用来设置当前线程中变量的副本
3、remove()用来移除当前线程中变量的副本
4、initialValue()是一个protected方法,显然是为了让子类覆盖而设计的,返回此线程局部变量的当前线程的初始值
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化。
在进行get之前,必须先set,否则会报空指针异常;
很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
ThreadLocal的使用方法和实现原理。
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储
出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。
一个线程不需要关心其他线程是否对这个connect进行了修改的。
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。
内部维护了一个ThreadLocalMap。
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
ThreadLocal是什么
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
/**
* Created on 2020/4/9 19:05
* author:crs
* Description: 测试ThreadLocal的使用
*/
public class TestThreadLocal {
ThreadLocal intThread= new ThreadLocal<Integer>();
ThreadLocal strThread= new ThreadLocal<String>();
public void init(){
//当前线程id和当前线程名称
intThread.set(Thread.currentThread().getId());
strThread.set(Thread.currentThread().getName());
}
public Long getLong(){
return (Long) intThread.get();
}
public String getString(){
return (String) strThread.get();
}
public static void main(String[] args) throws InterruptedException {
TestThreadLocal testThreadLocal = new TestThreadLocal();
testThreadLocal.init();
System.out.println(testThreadLocal.getLong());
System.out.println(testThreadLocal.getString());
System.out.println("--->");
Thread thread = new Thread(){
@Override
public void run() {
//设置当前子线程的id和名称
testThreadLocal.init();
System.out.println(testThreadLocal.getLong());
System.out.println(testThreadLocal.getString());
}
};
thread.start();
thread.join();
System.out.println("------>");
System.out.println(testThreadLocal.getLong());
System.out.println(testThreadLocal.getString());
}
}
ThreadLocal可能造成的内存泄露,如何解决,底层使用了哪种引用方式?
1、实现多个线程之间的资源互相隔离,达到安全并发的目的。
2、ThreadLocal主要的是用于独享自己的变量,避免一些资源的争夺,从而实现了空间换时间的思想。而synchronised则主要用于临界(冲突)资源的分配,从而能够实现线程间信息同步,公共资源共享等
3、变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;
造成内存泄露的原因
1、ThreadLocal中用到的自己定义的Map(和常用的Map接口不同)中
2、使用的Key值是一个WeakReference类型的值(弱引用会在下一次GC时马上释放而不管是否被引用)。
3、那么如果这个Key在GC时被释放了,就会导致Value永远都不会被调用到,但是如果线程不结束,又一直存在。
ThreadLocalMap是ThreadLocal自己实现的一个Map,而这个Map用使用了ThreadLocal作为了一个弱引用的Key。
其实每个Thread里面都有一个Map,Map里面的Key是ThreadLocal类的一个实例,之所以会比较混淆主要还是因为这里的Map又是ThreadLocal里面的一个内部静态类。
为什么key使用弱引用?(key是当前ThreadLocal的实例)
ThreadLocal@123456对象只是作为ThreadLocalMap的一个key而存在的,现在它被回收了,但是它对应的value并没有被回收,内存泄露依然存在!而且key被删了之后,变成了null,value更是无法被访问到了!针对这一问题,ThreadLocalMap类的设计本身已经有了这一问题的解决方案,那就是在每次get()/set()/remove()ThreadLocalMap中的值的时候,会自动清理key为null的value。如此一来,value也能被回收了。
内存泄露(本该回收的无用对象没有得到回收)