ThreadLocal
- 为解决多线程并发问题提供一种新思路。ThreadLocal并不是一个Thread,而是Thread局部变量
ThreadLocal作用
- 解决多线程环境下整个上下文调用需要将关键参数透传
- 如果不使用ThreadLocal,每个方法都要加关键参数,如果内部方法链路过长,那么代码看起来冗余、臃肿
- 如果某处传时将参数值改掉或设置为null,后续调用方法中用到这个参数的代码会受到影响
- 未用ThreadLocal的例子,模拟HTTP服务器接收用户请求
package com.myd.cn.ThreadLocal; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NoThreadLocalHTTPServer { public static void main(String[] args) { simpleTest(); } public static void simpleTest(){ ExecutorService executorService = Executors.newFixedThreadPool(2); //提交线程任务 executorService.submit(()->{ Long userId = 10000L; doUserRequest(userId); }); executorService.submit(()->{ Long userId = 20000L; doUserRequest(userId); }); } private static void doUserRequest(Long userId) { ThreadContext threadContext = new ThreadContext(); threadContext.setUserId(userId); String info = getCusInfo(threadContext); List<String> courses = getMyCourse(threadContext); System.out.println("user info "+info +";courses = "+courses.toString()); } private static String getCusInfo(ThreadContext threadContext){ return threadContext.getUserId() +" 的UserId信息"; } private static List<String> getMyCourse(ThreadContext threadContext){ return Collections.singletonList(threadContext.getUserId()+" 相应的课程"); } public static class ThreadContext{ private Long userId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } } }
- 使用ThreadLocal例子
package com.myd.cn.ThreadLocal; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLocalHTTPServer { public static void main(String[] args) { simpleTest(); } private static void simpleTest() { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(()->{ Long userId = 1000L; doUseRequeset(userId); }); executorService.submit(()->{ Long userId = 2000L; doUseRequeset(userId); }); } private static void doUseRequeset(Long userId) { UserSessionContext.getUserSessionContext().setUserId(userId); String info = getCusInfo(); List<String> courses = getCourse(); System.out.println("user info "+info +";courses = "+courses.toString()); } public static String getCusInfo(){ return UserSessionContext.getUserSessionContext().getUserId()+" 的相关信息"; } public static List<String> getCourse(){ return Collections.singletonList(UserSessionContext.getUserSessionContext().getUserId()+"的 相关课程信息"); } private static class UserSessionContext{ private static ThreadLocal<UserSessionContext> threadLocal = ThreadLocal.withInitial(UserSessionContext::new); //获取ThreadLocal包含的(线程本地存储)的变量UserSessionContext public static UserSessionContext getUserSessionContext(){ return threadLocal.get(); } /** * 删除本地存储的变量,线程执行完毕时删除,避免内存溢出 */ public static void removeContext(){ threadLocal.remove(); } private Long userId; public static ThreadLocal<UserSessionContext> getThreadLocal() { return threadLocal; } public static void setThreadLocal(ThreadLocal<UserSessionContext> threadLocal) { UserSessionContext.threadLocal = threadLocal; } public Long getUserId() { return userId; } public void setUserId(Long userId) { System.out.println(Thread.currentThread().getName()+ " set userId = "+userId); this.userId = userId; } } }
ThreadLocal原理
- java四种引用方式
- 弱引用例子:对象被弱引用后置空,JVM触发GC会将其回收
1.代码
package com.myd.cn.ThreadLocal;
import java.lang.ref.WeakReference;
/**
* 测试弱引用,弱引用的对象并没有被其他对象直接引用,
* 那么对于ThreadLocal->threadlocals->threadLlocalMap->key的弱引用被回收,相应强引用的value则不会被回收,造成OOM
* @author dymll
*
*/
public class WeakReferenceOfThreadLocal {
public static void main(String[] args) throws Exception {
testWeakReferenceNoDirectReference() ;
}
public static void testWeakReferenceNoDirectReference() throws Exception{
Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<Object>(obj);
System.out.println(weakReference.get());
obj = null;
//将obj变量置空 ,触发手动GC,后续引用为空
System.gc();
System.out.println(weakReference.get());
}
}
2.结果,查看被list对象引用obj仍有输出
java.lang.Object@15db9742
null
````
- 如果对象被弱引用,且其后又被其他对象直接引用(强引用),则后面即使被置空,也不会被触发的GC操作回收掉
1.代码 package com.myd.cn.ThreadLocal; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * 测试弱引用,弱引用的对象并没有被其他对象直接引用, * 那么对于ThreadLocal->threadlocals->threadLlocalMap->key的弱引用被回收,相应强引用的value则不会被回收,造成OOM * @author dymll * */ public class StrongReferenceAfterWeakReference { public static void main(String[] args) throws Exception { testWeakReferenceNoDirectReference() ; } public static void testWeakReferenceNoDirectReference() throws Exception{ Object obj = new Object(); WeakReference<Object> weakReference = new WeakReference<Object>(obj); System.out.println(weakReference.get()); List<Object> objects = new ArrayList<>(); //若在弱引用之后又有直接引用(强引用),则及时被置空,也不会被GC objects.add(obj); obj = null; //将obj变量置空 ,触发手动GC,后续引用为空 System.gc(); System.out.println(weakReference.get()); } }
2.结果,查看被list对象引用obj仍有输出
java.lang.Object@15db9742
java.lang.Object@15db9742
## ThreadLocal数据结构
- 查看其源码,ThreadLocal数据存在其下Threadlocals的ThreadLocalMap,Map有Entry构成,Entry包含每个线程作为key,value是每个线程专有的数据(这里是变量)

- 每个线程都有自己的ThreadLocal

## ThreadLocal OOM问题
- key是软引用,被置空后会被回收,那么Thread->ThreadLocalMap->Entry->Value这个链路是强引用链路,ThreadLocalMap无法再通过key来访问value,value在这里不会被回收,当这样的对象过多占用内存是,发生OOM

- 如果解决ThreadLocal OOM问题
- 在使用ThreadLocal 时,都要在线程全部指向完毕后,在finally代码块中调用remove()方法,清除内存(线程池中尤为注意要清除内存,更易因为强引用无法回收内存,造成OOM)
- 保存在ThreadLocal的数据不能过大