目 录CONTENT

文章目录

对于ThreadLocal的认识

在水一方
2022-02-16 / 0 评论 / 0 点赞 / 611 阅读 / 4,716 字 / 正在检测是否收录...

java.lang包下的一个类,设计的目的是为了解决并发时,线程变量共享的问题,对于线程变量共享这个部分知识基本上是盲区,实际项目中也会用到这个ThreadLocal对象,我们项目中保存像用户信息这种全局变量就是通过ThreadLocal来实现的

注意不能将其翻译为线程本地化或本地线程,英语恰当的名称应该叫作:CopyValueIntoEveryThread

ThreadLocal中的两个静态内部类

  • ThreadLocalMap,内部使用Entry做为存储数据的结构
  • Entry:是ThreadLocalMap类中的静态内部类,继承了WeakReference(弱引用)
   static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

引用类型

强引用,软引用,弱引用,虚引用

ThreadLocal的价值

ThreadLocal的副作用

1 🔔🔔 脏数据
线程的复用会产生脏数据

2 🔥🔥 内存泄漏
分析:在源码注释中提示使用static关键字来修饰ThreadLocal。在此场景下当ThreadLocal对象失去引用后,触发弱引用机制来回收Entry的value就不现实了

image.png

解决方案:每次用完ThreadLocal时必须要及时调用remove()方法清理

注意事项

ThreadLocal对象通常作为静态私有变量来使用,那么其生命周期不会随着线程的结束而结束
线程使用ThreadLocal有3个重要方法
1 set() 如果没有set方法操作ThreadLocal,容易引起脏数据问题
2 get()方法,始终没有get操作的ThreadLocal对象是没有意义的
3 remove() 如果没有remove方法,容易引起内存泄露

项目中是怎么用的

先定义一个FilterContextHandler 对象,在对象中定义一个ThreadLocal对象,设置set和get方法以及remove方法

public class FilterContextHandler {
    // ThreadLocal中放置map或者对象都可以
    public static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();

    public static void set(String key, Object value) {
        Map<String, Object> map = threadLocal.get();
        if (map == null) {
            map = new HashMap<String, Object>();
            threadLocal.set(map);
        }
        map.put(key, value);
    }

    public static Object get(String key) {
        Map<String, Object> map = threadLocal.get();
        if (map == null) {
            map = new HashMap<String, Object>();
            threadLocal.set(map);
        }
        return map.get(key);
    }

    public static String getUserID() {
        Object value = get(CommonConstants.CONTEXT_USER_ID);
        return returnObjectValue(value);
    }

    public static String getOrgID() {
        Object value = get(CommonConstants.CONTEXT_ORG_ID);
        return returnObjectValue(value);
    }

    public static String getUsername() {
        Object value = get(CommonConstants.CONTEXT_USERNAME);
        return returnObjectValue(value);
    }


    public static String getName() {
        Object value = get(CommonConstants.CONTEXT_NAME);
        return returnObjectValue(value);
    }

    public static String getToken() {
        Object value = get(CommonConstants.CONTEXT_TOKEN);
        return returnObjectValue(value);
    }

    public static void setToken(String token) {
        set(CommonConstants.CONTEXT_TOKEN, token);
    }

    public static void setName(String name) {
        set(CommonConstants.CONTEXT_NAME, name);
    }

    public static void setUserID(String userID) {
        set(CommonConstants.CONTEXT_USER_ID, userID);
    }

    public static void setOrgID(String orgID) {
        set(CommonConstants.CONTEXT_ORG_ID, orgID);
    }

    public static void setUsername(String username) {
        set(CommonConstants.CONTEXT_USERNAME, username);
    }

    private static String returnObjectValue(Object value) {
        return value == null ? null : value.toString();
    }

    public static void remove() {
        threadLocal.remove();
    }

}

2 在拦截器的prehandle()中解析token然后将得到的用户信息存储到FilterContextHandler对象中

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token =
                request.getHeader(CommonConstants.CONTEXT_TOKEN);
        if(!StringUtils.isNotBlank(token)){
            token = request.getParameter("Authorization");
        }
        if(StringUtils.isNotBlank(token)) {
            UserToken userToken =
                    JwtUtils.getInfoFromToken(token);
            FilterContextHandler.setToken(token);
            String username = userToken.getUsername();
            String name = userToken.getName();
            String userId = userToken.getUserId();
            String orgId = userToken.getOrgId();

            UserToken userTokennew = new UserToken(username, userId, username, orgId);
            token = JwtUtils.generateToken(userToken, 2*60*60*1000);

            logger.info("------设置token" + Thread.currentThread().getId());
            FilterContextHandler.setUsername(userToken.getUsername());
            FilterContextHandler.setName(userToken.getName());
            FilterContextHandler.setUserID(userToken.getUserId());
            FilterContextHandler.setOrgID(userToken.getOrgId());
        }

        return super.preHandle(request, response, handler);
    }


3 项目中用到的需要获取用户信息时只需要调用对应的get方法即可
FilterContextHandler.getUserID()

spring中的应用

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:
image.png

 private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");

Spring的事务主要是ThreadLocal和AOP去做实现的

FilterChain的子类中也用到了ThreadLocal
image.png

未完待续......

0

评论区