并发编程
1、同一线程,不同组件中传递数据。
2、线程隔离,每一个线程都是独立的,互不影响。
结构及用法
1、ThreadLocal 的内部 ThreadLocalMap,键为 ThreadLocal。
2、ThreadLocal 的数据结构是个环形数组
3、get,set 两个方法都不能完全防止内存泄漏,还是每次用完 ThreadLocal 都勤奋的 remove一下靠谱。
4、ThreadLocalMap 采用开放地址法,ThreadLocal 往往存放的数据量不会特别大(而且key 是弱引用又会被垃圾回收,及时让数据量更小),这个时候开放地址法简单的结构会显得更省空间,同时数组的查询效率也是非常高,加上第一点的保障,冲突概率也低。
ThreadLocalMap 的 Entry 的 key 是弱引用,如果外部没有强引用指向 key,key 就会被回收,而 value 由于 Entry 强引用指向了它,导致无法被回收,但是 value 又无法被访问,因此发生内存泄漏。
关于内存泄漏,我们重点从源码层面分析了 get、set、remove 方法,并图文并茂的演示了 get、set 方法不能防止内存泄漏,而 remove 方法能防止内存泄漏的结论。
问题1
1、ThreadLocal中有有使用弱引用,为什么要用弱引用?用弱引用,发生一次gc后,set进去的值再get就是null了吗?
图可以看出,ThreadLocal作为key,是有两条引用链的,一条是当前线程中的,由线程指向ThreadLocalMap,通过Map指向Entry,而Entry指向key;另一条引用链则是当前执行的主线程类的成员变量,且为强引用,所以目前来说并不会受到gc影响。
问题2
2、内存泄露直至OOM?
public class TestThreadLocalLeak {
final static ThreadLocal<byte[]> LOCAL = new ThreadLocal();
final static int _1M = 1024 * 1024;
public static void main(String[] args) {
//testUseThread();
testUseThreadPool();
}
/**
* 使用线程
*/
private static void testUseThread() {
for (int i = 0; i < 100; i++) {
new Thread(() ->
LOCAL.set(new byte[_1M])
).start();
}
}
/**
* 使用线程池
*/
private static void testUseThreadPool() {
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executorService.execute(() ->
LOCAL.set(new byte[_1M])
);
}
executorService.shutdown();
}
}
当调用testUseThread()时,系统在运行时执行了大量YGC,但始终稳定回收,最后正常执行,但是执行testUseThreadPool()时,经历的频繁的Full GC,内存却没有降下去,最终发生了OOM。
我们分析一下,在使用new Thread()的时候,当线程执行完毕时,随着线程的终止,那个这个Thread对象的生命周期也就结束了,此时该线程下的成员变量,ThreadLocalMap是GC Root不可达的,同理,下面的Entry、里面的key、value都会在下一次gc时被回收;而使用线程池后,由于线程执行完一个任务后,不会被回收,而是被放回线程池以便执行后续任务,自然其成员变量ThreadLocalMap不会被回收,最终引起内存泄露直至OOM。至于怎么避免出现内存泄露,就是在使用线程完成任务后,如果保存在ThreadLocalMap中的数据不必留给之后的任务重复使用,就要及时调用ThreadLocal的remove(),这个方法会把ThreadLocalMap中的相关key和value分别置为null,就能在下次GC时回收了。
参见:https://mp.weixin.qq.com/s/hMCVtX0bQXy38uBJfXgR2g
传递用户信息
package com.example.study.interceptor;
public class LoginInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookieList = request.getCookies();
if (cookieList == null) {
return false;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals("userToken")) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
break;
}
}
} catch (UnsupportedEncodingException e) {
logger.error("Cookie Decode Error.", e);
}
if (retValue == null) {
// token为空,用户未登录
CurrentSysUser.removeCurrentSysUser();
response.sendRedirect("login");
return false;
}
String userId = redis.get(retValue);
if (userId == null) {
// operator为空,用户登录超时
CurrentSysUser.removeCurrentSysUser();
response.sendRedirect("login");
return false;
}
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(sysUser, operator);
sysUser.setUserAccount(operator.getUserCode());
sysUser.setCompanyId(operator.getCompanyCode());
sysUser.setCompanyName(operator.getCompanyName());
sysUser.setSysPositionId(operator.getRoleCode());
sysUser.setSysPositionName(operator.getRoleName());
sysUser.setOrgId(operator.getOrgCode());
RoleWorkData workData = operator.getRole().getWorkData();
HashSet<String> dataSet = workData.getStrDataSet(RoleWorkData.DATATYPE_DEMAND_SIDE);
sysUser.setDemandSide(dataSet);
sysUser.setSaleChannel(workData.getStrDataSet(RoleWorkData.DATATYPE_SALE_CHANNEL));
sysUser.setPopShopCodes(workData.getStrDataSet(RoleWorkData.DATATYPE_POP_SHOP_CODE));
// 用户已登录
CurrentSysUser.setCurrentSysUser(sysUser);
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (ex != null) {
response.setStatus(500);
logger.error("system.error", ex);
}
CurrentSysUser.removeCurrentSysUser();
}
}
public class CurrentSysUser {
private static ThreadLocal<SysUser> sysUsers = new ThreadLocal<SysUser>();
public static void setCurrentSysUser(SysUser sysUser){
sysUsers.set(sysUser);
}
public static SysUser getCurrentSysUser(){
return sysUsers.get();
}
public static void removeCurrentSysUser(){
sysUsers.remove();
}
public static String getCurrentSysUserAccount(){
return sysUsers.get().getUserAccount();
}
}