【Java基础】ThreadLocal解析

ThreadLocal 用法

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

  使用场景:

    1. 在进行对象跨层传递的时候,使用threadLocal可以避免多次传递,打破层次间的约束。
    2. 线程间数据隔离。
    3. 进行事务操作,用于存储线程事务信息。
    4. 数据库连接,Session会话管理。(spring框架在事务开始时会给当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是ThreadLocal来实现的)

  ThreadLocal 代码示例

    示例业务场景:用于存放用户信息,以便后续查询业务中获取用户信息
      1. 定一个UserManager类

	public class UserManager {
	
		private static ThreadLocal<User> userLocal = new ThreadLocal<User>();
		
		public static User getUser() {
			return userLocal.get();
		}
		
		public static void setUser(User user) {
			userLocal.set(user);
		}
		
	}

    2. 定义一个拦截器每次请求来的时候存 将用户信息放入 ThreadLocal中

@Slf4j
public class DemoHttpInterceptor extends  GenericFilterBean  {
	@Autowired
	UserService userService;
	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		
		final HttpServletRequest request = (HttpServletRequest) req;
		final HttpServletResponse response = (HttpServletResponse) res;
		
		String usercode = request.getHeader("iv-user"); //前端传过来员工号
		log.info("------>>>DemoHttpInterceptor  " + usercode );
		
		loginUser = userService.findUserByStaffcode(usercode );	//数据库中查找用户信息
		
		UserManager.setUser(loginUser);		// 将用户信息放入 ThreadLocal中
		chain.doFilter(req, res);
	}
}

    3. 在业务代码中获取用户信息

@Slf4j
@RestController
@RequestMapping("/learning")
public class LearningInfoController {
    @Autowired
    private LearningInfoService learningInfoService;

    @RequestMapping(method = RequestMethod.POST)
    public RestResult<PdsLearninginfo> addLearningInfo(@Valid @RequestBody PdsLearninginfo learninginfoModel, BindingResult bindingResult) {
       
        PdsUser loginUser = UserManager.getUser(); // 从ThreadLocal中获取用户信息
        return RestResultGenerator.genSuccessResult(this.learningInfoService.save(learninginfoModel, loginUser));
        
    }
}

ThreadLocal 数据结构设计

  1. 每个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储在本线程中所有的ThreadLocal对象以及其对应的值;
在这里插入图片描述
  2. ThreadLocalMap由一个个Entry对象构成,Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

  3. set方法,执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap中。

//JDK1.8 源码, java.lang.ThreadLocal
public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 然后获取当前线程的ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

  4. get方法,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

//JDK1.8 源码, java.lang.ThreadLocal
public T get() {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 然后获取当前线程的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocal内存泄漏的原因,如何避免

 内存泄漏:不再会被使用的对象占用的内存不能被回收。

  ThreadLocal中主要的存储单元Entry类继承了WeakReference,弱引用即时没有手动删除,ThreadLocal也会被回收,但是对于value如果是强引用类型,就需要进行手动remove,避免value的内存泄露。
  如果一个ThreadLocal不存在外部强引用时,key势必会被GC回收,这样就会导致ThreadLocalMap为null,而Value还存在强引用,只有这个线程退出后,value的强引用链才会断掉,如果线程迟迟不结束,这个key为null的value就会一直存在一条强引用链。

在这里插入图片描述

 ThreadLocal的正确使用方式

   1. 每次使用完ThreadLocal都调用它的remove()方法清除数据。
   2. 将ThreadLocal变量定义成 private static, 这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的Value值,进而清除掉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值