- 什么时候需要使用ThreadLocal
- 每个线程需要一个独享的对象,选择initialValue保存对象(通常是工具类,典型需要的类有SimpleDateFormat和Random)
- 每个线程需要保存全局变量,可以让不同方法直接调用,避免参数传递的麻烦,用set方法保存对象
第一种
/**
* 利用threadLocal给每个线程分配自己的dataformat对象,保证了线程安全,高效利用线程。
*/
public class ThreadLocalNormalUsege05 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<1000;i++){
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalNormalUsege05().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds){
//参数的单位是毫秒,从1970.1.1 00:00:00开始
Date date = new Date(seconds * 1000);
SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.simpleDateFormatThreadLocal2.get();
return simpleDateFormat.format(date);
}
}
class ThreadSafeFormatter{
public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
//lamda表达式写法
public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}
第二种
/**
* 演示threadlocal用法2:避免参数的层层传递
*/
public class ThreadLocalNormalUsege06 {
public static void main(String[] args) {
new Service1().process();
}
}
class Service1 {
public void process() {
User user = new User("超哥");
UserContextHolder.holder.set(user);
new Service2().process();
}
}
class Service2 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service2拿到用户名:" + user.name);
// UserContextHolder.holder.remove();
// User user1 = new User("王姐");
// UserContextHolder.holder.set(user1);
new Service3().process();
}
}
class Service3 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service3拿到用户名:" + user.name);
UserContextHolder.holder.remove();
}
}
class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<User>();
}
class User {
String name;
public User(String name) {
this.name = name;
}
}
- 场景一:initialValue
在ThreadLocal第一次get的时候把对象初始化出来,对象初始化时机可以由我们控制。 - 场景二:set
如果需要保存到ThreadLocal里的对象生成时机不由我们随意控制,例如拦截器生成的用户数据,用ThreadLocal.set将数据直接存放到ThreadLocal中去,以便后续使用。 - 使用ThreadLocal的好处
- 线程安全
- 不需要加锁,提高效率
- 更高效的利用内存,节省开销:相比每个任务新建一个SimpleDateFormat,上面用ThreadLocal更节省内存和开销
- 免去传参的繁琐,无论场景一的工具类或者场景二的用户信息,都可以在任意地方通过ThreadLocal拿到,再也无需每次传相同的参数,ThreadLocal使得代码耦合性更低更优雅。
- Thread、ThreadLocal和ThreadLocalMap关系
- 主要方法介绍
- initialValue()
- 该方法会返回对应线程的“初始值”,这是一个延迟加载的方法,只有在调用get()的时候才会触发。
- 当线程第一次调用get()方法访问变量时,将调用此方法,除非线程先前调用了set()方法,在这种情况下,不会为线程调用本initialValue()。
- 通常线程会调用一次此方法,但是如果调用了remove方法,再调用get方法,则可以再次调用此方法。
- 如果不重写此方法,这个方法会返回null。一般使用匿名内部内的方法来重写此方法,以便在后续使用中可以初始化副本对象。
- get()
- get()方法是先取出当前线程的ThreadLocalMap,然后调用Map.getEntry方法,吧ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的Value。
- 注意,这个map以及map的key和value是保存在线程中的,而不是保存在ThreadLocal中。
- ThreadLocalMap类
- ThreadLocalMap类是每个线程Thread类里面的变量,里面最重要的是一个键值数组Entry[] table,可以认为是一个map,键值对:
- 键:这个ThreadLocal
- 值:实际需要的成员变量,比如user或者SimpleDateFormat对象
- 内存泄露
某个对象不再有用,但是占用的内存却不能被回收,
Value的泄露:- ThreadLocalMap的每个Entry都是一个对key的弱引用,同时每个Entry都包含一个对Value的强引用。
- 正常情况下,当线程终止,保存在ThreadLocal里的Value会被垃圾回收,因为没有任何强引用了。
- 但是,如果线程不终止(比如线程需要保持很久),那么key对的Value就不能被回收,因为有以下调用链:
Thread -> ThreadLocalMap -> Entry(key为null) -> Value - 因为value和Thread之间还存在这个强引用链路,所以导致value无法回收,就可能出现OOM。
- JDK已经考虑到这个问题,所以set、remove、rehash方法中会扫描key为null的Entry,并把对应的Value设置成null,这样value对象就能被回收了。
- 但是如果一个线程ThreadLocal不被使用,那么实际上set、remove、rehash就不会再被调用,如果同时线程又不停止,那么调用链就一直存在,就导致了value的内存泄露。
- 避免内存泄露
- (阿里规定)调用remove方法,就会删除对应的Entry对象,可以避免内存泄露,所以使用完ThreadLocal之后,应该调用remove方法。
- ThreadLocal注意点
- 如果可以不使用ThreadLocal就能解决问题就不要强行使用。
- 优先使用框架的支持,而不是自己创造,例如在spring中,如果可以使用RequestContextHolder,那么就不需要自己维护ThreadLocal,防止忘记调用remove()方法,造成内存泄露。
- 在get()之前,必须set(),否则可能会报空指针异常。
- ThreadLocal不能塞入共享对象,比如static对象,因为这样ThreadLocal.get()取得的还是这个共享对象本身,还是会发生并发访问问题。