一.ThreadLocal线程变量的实现原理

1.ThreadLocal核心方法有这个几个

get()、set(value)、remove()

2.实现原理

ThreadLocal在每个线程都会创建一个线程内对应的T的副本,本T数据可以在本线程内任何地方可以被使用。线程之间互相不影响,所以是线程安全的。

3.底层结构

ThreadLocal实现各个线程数据副本的存取,是通过操作它的内部类ThreadLocalMap,进行<k,v>键值对的存取和移除。

4.set(value)方法的底层

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }
  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }
  1. private void set(ThreadLocal<?> key, Object value) {
  2.  
  3. // We don't use a fast path as with get() because it is at
  4. // least as common to use set() to create new entries as
  5. // it is to replace existing ones, in which case, a fast
  6. // path would fail more often than not.
  7.  
  8. Entry[] tab = table;
  9. int len = tab.length;
  10. int i = key.threadLocalHashCode & (len-1);
  11.  
  12. for (Entry e = tab[i];
  13. e != null;
  14. e = tab[i = nextIndex(i, len)]) {
  15. ThreadLocal<?> k = e.get();
  16.  
  17. if (k == key) {
  18. e.value = value;
  19. return;
  20. }
  21.  
  22. if (k == null) {
  23. replaceStaleEntry(key, value, i);
  24. return;
  25. }
  26. }
  27.  
  28. tab[i] = new Entry(key, value);
  29. int sz = ++size;
  30. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  31. rehash();
  32. }

set(value)

  1》根据当前线程,获取本线程所拥有的TreadLocalMap,如果没有,则创建一个新的。

  2》ThreadLocalMap的<key,value>即<ThreadLocal对象,传入的value值>。【这里的ThreadLocal对象在set处,是根据本对象的hashCode经过计算获取到下标,然后循环对比Entry[]中每一个Entry的key进行插入或覆盖操作】

  3》那么可以看出结构是:

    3.1》每一个Thread有一个对应的ThreadLocalMap。Map的<K,V>即<当前ThreadLocal对象,传入的value>

    3.2》set操作根据ThreadLocal对象的hashCode对比Entry[]数组,进行新增插入或覆盖操作。

5.get()方法底层

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }
  1. private Entry getEntry(ThreadLocal<?> key) {
  2. int i = key.threadLocalHashCode & (table.length - 1);
  3. Entry e = table[i];
  4. if (e != null && e.get() == key)
  5. return e;
  6. else
  7. return getEntryAfterMiss(key, i, e);
  8. }
  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }
  1. protected T initialValue() {
  2. return null;
  3. }

get()

  1》根据当前Thread线程,获取本线程的ThreadLocalMap

  2》然后将<K>键,也就是本ThreadLocal作为键传入,从Map中获取value。【获取的过程即,根据ThreadLocal对象的hashCode经过计算获取下标,根据下标取出Entry[]数组中的具体值,返回结果】

  3》如果没有值,则返回null。

6.remove()底层

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }

remove()

  1》本线程结束以前,一定要调用remove,清除线程变量中本次的变量。防止内存泄漏

二.ThreadLocal使用场景

拦截器存储 调用接口的用户信息,在本次Request到达,处理,直到返回的本线程中,都可以使用线程变量中的用户信息。

1.定义线程变量

  1. public class RequestData {
  2.  
  3. //线程变量 租户对象
  4. public static final ThreadLocal<TenementUser> TENEMENT_USER = new ThreadLocal<TenementUser>();

2.到达controller之前的拦截器中,赋值线程变量。request返回之前remove【防止内存泄漏】

  1. import java.net.URLDecoder;
  2. import java.net.URLEncoder;
  3.  
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6.  
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.springframework.web.servlet.HandlerInterceptor;
  9. import org.springframework.web.servlet.ModelAndView;
  10.  
  11. import com.alibaba.fastjson.JSON;
  12. import com.pisen.cloud.luna.core.enums.LunaHeaderNames;
  13. import com.pisen.cloud.luna.core.interceptor.utils.LunaInterceptorUtil;
  14. import com.pisen.cloud.luna.core.reqmodal.RequestData;
  15. import com.pisen.cloud.luna.core.utils.TenementUser;
  16.  
  17. public class TenementAuthinterceptor implements HandlerInterceptor{
  18.  
  19. @Override
  20. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  21. throws Exception {
  22.  
  23. String tenInfo = request.getHeader(LunaHeaderNames.TENEMENT_INFO.getName());
  24. TenementUser tuser = null;
  25.  
  26. if(StringUtils.isNotBlank(tenInfo)){
  27.  
  28. try {
  29. tenInfo = URLDecoder.decode(tenInfo, "UTF-8");
  30. tuser = JSON.parseObject(tenInfo,TenementUser.class);
  31. if(tuser != null){
  32.  
  33. if(StringUtils.isBlank(tuser.getTenementId()) || StringUtils.isBlank(tuser.getLoginName())){
  34. tuser = null;
  35. }else{
  36. RequestData.TENEMENT_USER.set(tuser);
  37. }
  38. }
  39.  
  40. } catch (Exception e) {
  41. e.printStackTrace();
  42. }
  43. }
  44.  
  45. if(tuser != null){
  46. return true;
  47. }else{
  48.  
  49. String errorMsg = "登录失败,请重新登录!";
  50. LunaInterceptorUtil.ErrorResp(response,errorMsg);
  51. return false;
  52. }
  53.  
  54. }
  55.  
  56. @Override
  57. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
  58. ModelAndView modelAndView) throws Exception {
  59. RequestData.TENEMENT_USER.remove();
  60. }
  61.  
  62. @Override
  63. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
  64. throws Exception {
  65. }
  66.  
  67. }

3.controller中使用线程变量

  1. //创建单据
  2. @RequestMapping(value = "/insert",method = RequestMethod.POST)
  3. public AjaxResult<SaleBill> insert(@RequestBody SaleBill bill){
  4.  
  5. TenementUser tuser = RequestData.TENEMENT_USER.get();

【java】ThreadLocal线程变量的实现原理和使用场景的更多相关文章

  1. 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)

    简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...

  2. java ThreadLocal线程设置私有变量底层源码分析

    前面也听说了ThreadLocal来实现高并发,以前都是用锁来实现,看了挺多资料的,发现其实还是区别挺大的(感觉严格来说ThreadLocal并不算高并发的解决方案),现在总结一下吧. 高并发中会出现 ...

  3. Java中线程池的实现原理

    知识点总结 ---------------------------------------------------------------------------------------------- ...

  4. Java中线程池的实现原理-求职必备

    jdk1.5引入Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行.被哪个线程执行,以及什么时候执行. 初始化线程池(4种) ...

  5. 线程池;java的线程池的实现原理;适用于频繁互动(如电商网站)

    线程池是一种多线程处理形式,处理过程中将任务加入到队列,然后在创建线程后自己主动启动这些任务.线程池线程都是后台线程.每一个线程都使用默认的堆栈大小,以默认的优先级执行.并处于多线程单元中. 假设某个 ...

  6. 线程变量ThreadLocal的使用

    我们有时候会通过token进行多次查询(猪:token是redis中的key),比如: 一次是在登录拦截器中,一次是在controller的业务中查询,这样存在性能和资源的浪费问题!!! 那么如何将拦 ...

  7. Java多线程——线程间通信

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  8. 线程池续:你必须要知道的线程池submit()实现原理之FutureTask!

    前言 上一篇内容写了Java中线程池的实现原理及源码分析,说好的是实实在在的大满足,想通过一篇文章让大家对线程池有个透彻的了解,但是文章写完总觉得还缺点什么? 上篇文章只提到线程提交的execute( ...

  9. Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理

    1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...

随机推荐

  1. java虚拟机规范(se8)——java虚拟机结构(一)

    本文翻译自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 第二章 虚拟机结构 本文档描述了一个抽象的虚拟机规范,并不描述 ...

  2. KNN算法的感受 1

    本来预计的打算是一天一个十大挖掘算法,然而由于同时要兼顾数据结构面试的事情,所以 很难办到,但至少在回家前要把数据挖掘十大算法看完,过个好年,在course上学习老吴的课程还是帮了我很大的忙,虽然浪费 ...

  3. knnMatch

    先马克下,回头再看:http://blog.csdn.net/zkl99999/article/details/47950425 http://blog.csdn.net/yangtrees/arti ...

  4. High-Speed Tracking with Kernelized Correlation Filters

          2015年的一篇论文,可参考:http://blog.csdn.net/carrierlxksuper/article/details/46461245.      另参考:http:// ...

  5. html圈圈

    <html> <head> <meta charset="utf-8" /> <link href="images/style. ...

  6. ERP渠道信息的修改和渠道联系记录(二十三)

    用例图: 前端代码: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Cha ...

  7. k8s的imagePullSecrets如何生成及使用

    如果公司的docker仓库(harbor),需要用户认证之后,才能拉取镜像. 那如何在k8s里生成这个secret呢? 这个secret如何还原呢? 在k8s的yaml文件里如何实现呢? 这里提供几个 ...

  8. 【LOJ】#121. 「离线可过」动态图连通性

    题解 和BZOJ4025挺像的 就是维护边权是时间的最大生成树 删边直接删 两点未联通时直接相连,两点联通则找两点间边权小的一条边删除即可 代码 #include <bits/stdc++.h& ...

  9. Codeforces Round #296 (Div. 1) B - Clique Problem

    B - Clique Problem 题目大意:给你坐标轴上n个点,每个点的权值为wi,两个点之间有边当且仅当 |xi - xj| >= wi + wj, 问你两两之间都有边的最大点集的大小. ...

  10. King Arthur's Birthday Celebration

    每天抛一个硬币,硬币正面朝上的几率是p,直到抛出k次正面为止结束,第一天抛硬币需花费1,第二天花费3,然后是5,7,9……以此类推,让我们求出抛硬币的天数的期望和花费的期望. 天数期望: A.投出了k ...