ThreadLocal作用
ThreadLocal的作用:用来存当前线程的局部变量,不同线程间互不干扰。拿完数据记得需要移除数据,不然JVM不会将ThreadLocal回收(可能还会被引用),多了就会出现内存泄漏的情况。
解析ThreadLocal类
ThreadLocal包含几个方法:
public T get()
public void set(T value)
public void remove()
protected T initialValue() T get()
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
set(T value)
set()用来设置当前线程中变量的副本
void remove()
remove()用来移除当前线程中变量的副本
T initialValue()
initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。
ThreadLocal变量污染
ThreadLocal会在每个线程存储一个副本,但是如果我们使用的是比如Tomcat,Tomcat自身会维护一个线程池,线程结束后,并不会马上销毁,而是会重新进入线程池,下次有请求时,有可能会复用当前线程,如果我们每次使用ThreadLocal之前,没有进行Set(T value),那么就有可能导致不同线程之间变量污染,比如下面的代码
@RestController
@RequestMapping(value = "book")
@Slf4j
public class BookController {
private static final ThreadLocal<String> THREAD_LOCAL_TEST = new ThreadLocal<>();
@Resource
private IBookService bookService;
/**
* 构造函数
*/
public BookController() {
log.info("构造函数,此时bookService :" + bookService);
}
@PostConstruct
public void init() {
log.info("PostConstruct,此时bookService :" + bookService);
}
@PreDestroy
public void destroy() {
log.info("PreDestroy");
}
@GetMapping(value = "set")
public void set() {
if (StringUtils.isEmpty(THREAD_LOCAL_TEST.get())) {
THREAD_LOCAL_TEST.set(UUID.randomUUID().toString());
}
}
@GetMapping(value = "search")
public List<Book> search() {
log.error(THREAD_LOCAL_TEST.get());
return bookService.search();
}
}使用jmeter进行请求,可以看到同一线程,输出的内容永远不会发生改变。
可以在内次使用之前进行set(T value),但是set(T value)可能会导致内存无法释放。
ThreadLocal可能导致的内存泄露
ThreadLocal为了避免内存泄露,不仅使用了弱引用维护key,还会在每个操作上检查key是否被回收,进而再回收value。
但是从中也可以看到,ThreadLocal并不能100%保证不发生内存泄漏。比如,很不幸的,你的get()方法总是访问固定几个一直存在的ThreadLocal,那么清理动作就不会执行,如果你没有机会调用set()和remove(),那么这个内存泄漏依然会发生。
一个良好的习惯依然是:当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的。
@RestController
@RequestMapping(value = "book")
@Slf4j
public class BookController {
private static final ThreadLocal<String> THREAD_LOCAL_TEST = new ThreadLocal<>();
@Resource
private IBookService bookService;
/**
* 构造函数
*/
public BookController() {
log.info("构造函数,此时bookService :" + bookService);
}
@PostConstruct
public void init() {
log.info("PostConstruct,此时bookService :" + bookService);
}
@PreDestroy
public void destroy() {
log.info("PreDestroy");
}
@GetMapping(value = "set")
public void set() {
THREAD_LOCAL_TEST.set(UUID.randomUUID().toString());
}
@GetMapping(value = "search")
public List<Book> search() {
log.error(THREAD_LOCAL_TEST.get());
THREAD_LOCAL_TEST.remove();
return bookService.search();
}
}ThreadLocal与局部变量
局部变量和ThreadLocal起到的作用是一样的,保证了并发环境下数据的安全性。那就是说,完全可以用局部变量来代替ThreadLocal咯?
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).翻译过来
ThreadLocal提供的是一种线程局部变量。这些变量不同于其它变量的点在于每个线程在获取变量的时候,都拥有它自己相对独立的变量初始化拷贝。ThreadLocal的实例一般是私有静态的,可以做到与一个线程绑定某一种状态。
所以就这段话而言,我们知道ThreadLocal不是为了满足多线程安全而开发出来的,因为局部变量已经足够安全。ThreadLocal是为了方便线程处理自己的某种状态。
可以看到ThreadLocal实例化所处的位置,是一个线程共有区域。好比一个银行和个人,我们可以把钱存在银行,也可以把钱存在家。存在家里的钱是局部变量,仅供个人使用;存在银行里的钱也不是说可以让别人随便使用,只有我们以个人身份去获取才能得到。所以说ThreadLocal封装的变量我们是在外面某个区域保存了处于我们个人的一个状态,只允许我们自己去访问和修改的状态。
评论 (0)