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).
[typing]翻译过来[/typing] ThreadLocal
提供的是一种线程局部变量。这些变量不同于其它变量的点在于每个线程在获取变量的时候,都拥有它自己相对独立的变量初始化拷贝。ThreadLocal
的实例一般是私有静态的,可以做到与一个线程绑定某一种状态。
所以就这段话而言,我们知道ThreadLocal
不是为了满足多线程安全而开发出来的,因为局部变量已经足够安全。ThreadLocal
是为了方便线程处理自己的某种状态。
可以看到ThreadLocal
实例化所处的位置,是一个线程共有区域。好比一个银行和个人,我们可以把钱存在银行,也可以把钱存在家。存在家里的钱是局部变量,仅供个人使用;存在银行里的钱也不是说可以让别人随便使用,只有我们以个人身份去获取才能得到。所以说ThreadLocal
封装的变量我们是在外面某个区域保存了处于我们个人的一个状态,只允许我们自己去访问和修改的状态。
评论 (0)