ThreadLocal应用场景分析

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数据结构](https://img-blog.csdnimg.cn/20200327000458779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2R5bWtrag==,size_16,color_FFFFFF,t_70)
- 每个线程都有自己的ThreadLocal
	![每个线程都有自己ThreadLocal](https://img-blog.csdnimg.cn/20200327000515974.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2R5bWtrag==,size_16,color_FFFFFF,t_70)
## ThreadLocal OOM问题
- key是软引用,被置空后会被回收,那么Thread->ThreadLocalMap->Entry->Value这个链路是强引用链路,ThreadLocalMap无法再通过key来访问value,value在这里不会被回收,当这样的对象过多占用内存是,发生OOM
	![强引用且无法进行回收,而不能进行内存释放,造成内存泄漏](https://img-blog.csdnimg.cn/20200327000544676.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2R5bWtrag==,size_16,color_FFFFFF,t_70)
- 如果解决ThreadLocal OOM问题
- 在使用ThreadLocal 时,都要在线程全部指向完毕后,在finally代码块中调用remove()方法,清除内存(线程池中尤为注意要清除内存,更易因为强引用无法回收内存,造成OOM)
- 保存在ThreadLocal的数据不能过大
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值