ThreadLocal学习笔记

【java】详解Java的类文件(class文件)结构 1.概述 转载:详解Java的类文件(class文件)结构 大家好,我是二哥呀,今天我拿了一把小刀,准备解剖一下 Java 的 class 文件。 CS 的世界里流行着这么一句话,“计算机科学领域的任何问题都可以通过增加一个中间层来解决”。对于 Java 来说,JVM 就是这么一个产物,“Write once, Run anywhere”之所以能实现,靠得就是 JVM,它能在不同的操作系统下运行同一份源代码编译后的 class 文件。 Java 是跨平台的,JVM 作为中间层,自然要针对不同的操作系统提. 阅读详情

一、ThreadLocal

概念

(个人理解)ThreadLocal是解决多线程问题的一种解决方案
线程安全问题是因为多个线程,可以共同操作同一个公共变量,而导致的公共变量数据安全问题。
解决方案:

  1. 加锁,同一个时间内,只能一个线程修改公共变量。
  2. 把公共变量放到每个线程内,线程独享。不存在操作公共变量的问题了。(ThreadLocal就是这种)

源码

ThreadLocal

案例

1. dynamic-datasource存储当前线程的数据源

(dynamic-datasource的源码)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.baomidou.dynamic.datasource.toolkit;

import java.util.ArrayDeque;
import java.util.Deque;
import org.springframework.util.StringUtils;

public final class DynamicDataSourceContextHolder {
    private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
        protected Object initialValue() {
            return new ArrayDeque();
        }
    };

    private DynamicDataSourceContextHolder() {
    }

    public static String peek() {
        return (String)((Deque)LOOKUP_KEY_HOLDER.get()).peek();
    }

    public static void push(String ds) {
        ((Deque)LOOKUP_KEY_HOLDER.get()).push(StringUtils.isEmpty(ds) ? "" : ds);
    }

    public static void poll() {
        Deque<String> deque = (Deque)LOOKUP_KEY_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            LOOKUP_KEY_HOLDER.remove();
        }

    }

    public static void clear() {
        LOOKUP_KEY_HOLDER.remove();
    }
}

2. 自定义存储当前数据源


public class DataSourceContextHolder {
    /** 环境 */
    private static final ThreadLocal<EnvTypeEnum> ENV_CONTEXT_HOLDER = new ThreadLocal<>();

    /** 数据源 */
    private static final ThreadLocal<DatabaseType> DATABASE_CONTEXT_HOLDER = new ThreadLocal<DatabaseType>();

    public static void setEnvType(EnvTypeEnum envType){
        ENV_CONTEXT_HOLDER.set(envType);
    }

    public static EnvTypeEnum getEnvType(){
        return ENV_CONTEXT_HOLDER.get();
    }

    public static void clearEnvType(){
        ENV_CONTEXT_HOLDER.remove();
    }

    public static void setDatabaseType(DatabaseType type) {
        DATABASE_CONTEXT_HOLDER.set(type);
    }

    public static DatabaseType getDatabaseType() {
        return DATABASE_CONTEXT_HOLDER.get();
    }
}


问题:子线程问题(如果使用了多线程)

ThreadLocal而言,子线程是拿不到父线程的TL对象的。
但是Thread里还维护了一个inheritableThreadLocals用来存放父线程的TL对象。
在这里插入图片描述

二、InheritableThreadlocal

概念

InheritableThreadlocal 是ThreadLocal的一个子类

原理:

在线程初始化的时候,会将当前线程的ThreadLocal放到InheritableThreadLocalMap。
然后在InheritableThreadLocal中,重写了childValue和getMap方法,用来返回inheritableThreadLocals,而不是threadLocals。即返回了线程初始化时,父线程塞进去的inheritableThreadLocals;
在这里插入图片描述

在这里插入图片描述

Thread初始化时的代码:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
       // 省略...
        Thread parent = currentThread();
      	// 省略...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       //省略...
    }

流程:

伪代码


// 新建一个InheritableThreadLocal对象
ThreadLocal itl = new InheritableThreadLocal<String>()
	
main-thread 线程start:
	
	// 此时,itl.getMap() 返回了 main线程的createInheritedMap对象,然后把“v1”字符串set了进去。
	// 即,现在main线程的inheritableThreadLocals是ThreadLocalMap,且这个map里有一个key为InheritableThreadLocal对象(我们new的itl对象的threadLocalHashCode),value为“v1”字符串的值。
	itl.set("v1");
	
	// 新建一个线程,child_thead_1
	new Thread(){
		name = "child_thead_1";
		ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
		init(){
			 // 拿到main-thread的线程对象
			Thread parent = currentThread();
			
			// 复制一份main线程的inheritableThreadLocals(ThreadLocalMap)到自己的 inheritableThreadLocals(ThreadLocalMap)
			this.inheritableThreadLocals  = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
		}
	}.start(){
		run(){
			// child_thead_1 start
			// InheritableThreadLocal.getMap,返回了 child_thead_1  的 inheritableThreadLocals ;
			// 返回的Map里 去get key=InheritableThreadLocal(我们new的itl对象)的值,肯定会返回“v1”
			itl.get();
		}
	}

综上,就可以在子线程中,拿到父线程的ITL对象的value了。

InheritableThreadlocal

可继承的ThreadLocal;
Inheritable是在Thread对象中单独存储的一个Map,与ThreadLocalMap一样。
在Thread初始化的时候,会把父线程(当前线程)的ThreadLocal放到InheritableThreadLocalMap中。
一旦Thread初始化之后,Map里的ITL就不会再被赋值了。所以线程池里的线程只初始化一次,就会导致,ITL在线程池中失效。

问题:InheritableThreadLocal遇线程池失效

原因

我们综上可知,InheritableThreadLocal是在Thread对象初始化的时候,才将父线程的InheritableThreadLocalMap放到子线程的InheritableThreadLocalMap里。那么线程池的线程都是新建之后,可以复用的。所以肯定会有问题。
如:一旦子线程初始化了InheritableThreadLocalMap,就再也不会改变InheritableThreadLocalMap的值了。

问题描述:https://zhuanlan.zhihu.com/p/473659523
上边帖子的解决方案: 线程池不用new Thread,但是肯定需要new Runnable吧,所以对Runnable进行封装,在初始化Runnable的时候,通过【反射】对当前线程(即父线程)的ITL进行备份,然后在run的时候,拿到这个ITL 并塞到当前线程(即子线程)里。

解决

简书的github加速: https://gitcode.net/mirrors/alibaba/transmittable-thread-local?utm_source=csdn_github_accelerator

  1. 方法1:自定义Runable实现类
    https://www.jianshu.com/p/29f4034f4250

  2. 方法2:用阿里的TransmittableThreadLocal
    https://www.cnblogs.com/sweetchildomine/p/8807059.html

三、transmittable-thread-local 使用

Transmittable-ThreadLocal 是阿里的

ThreadLocal:https://www.cnblogs.com/hama1993/p/10382523.html
InheritableThreadLocal:https://www.cnblogs.com/hama1993/p/10400265.html
TransmittableThreadLocal:https://www.cnblogs.com/hama1993/p/10409740.html

https://www.cnblogs.com/cb1186512739/p/14214302.html

https://baijiahao.baidu.com/s?id=1702913441482159358&wfr=spider&for=pc

https://zhuanlan.zhihu.com/p/302371614

transmittableThreadLocal 继承了InheritableThreadLocal

TransmittableThreadLocal工作流程简介

ThreadLocal ttl= new TransmittableThreadLocal<String>();
// 即:不管每次set还是get都会将当前的ttl对象放到holder里存起来;且key是ttl对象,value是null,当一个set使用。
并且,这个holder是static的,即所有TransmittableThreadLocal对象,公用同一个holder,里边存所有的ttl对象

// 即在InheritableThreadLocal里,又维护了一个InheritableThreadLocal,并且这个InheritableThreadLocal里放的是当前对象TransmittableThreadLocal。 (维护了一个线程级别的InheritableThreadLocal,可以记录到每个线程的TransmittableThreadLocal对象。)

 private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
        protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
            return new WeakHashMap();
        }

        protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
            return new WeakHashMap(parentValue);
        }
    };
  
   

并且TransmittableThreadLocal 里有个很重要的Transmitter内部类,用来维护ThreadLocal的值。
在这里插入图片描述

  • capture方法:

    capture方法,在ttlRunable类里调用,即父线程调用。
    遍历holder里所有的ttl,然后调用ttl的copyValue时,会调当前线程的ttl值。即ttl.get(),并且复制一份,存起来。
    存到ttlRunable的成员属性captureRef里。
    
  • replay方法:

    在run方法里调用,即子线程。
    replay方法遍历holder,拿子线程的ttl值,放到backup里,然后遍历所有父线程的ttl对象和值,都去调用对应的ttl的set方法,把父线程的值放到子线程的ttl对象里。(描述不准确,其实是放到子Thread的inheritableThreadLocals里。为了便于理解,表述为放到ttl对象里)
    然后子线程再get,就会拿到父线程里的值了。
    
    
  • restore方法

    与replay类似,但是操作相反,删除现在所有的ttl对象的值,然后把backup里的值都恢复进来。
    
注意:必须使用Ttl包装类,来包装线程池的runable或者callable,否则TTL与ITL无异。

自定义 TtlRunnable 实现 Runnable,TtlRunnable初始化方法中保持当前线程中已有的TransmittableThreadLocal。

在这里插入图片描述

在这里插入图片描述

使用案例

场景

两个环境,uat和pro,在uat发布数据到pro的时候,客户端涉及到这些同步数据的数据库表都不让访问。
并且发布的时候,只某一个projectId下的所有数据,其它projectId的用户正常访问。

解决方案

难点:需要知道当前访问的projectId是什么,又不能修改每个方法去传递这个参数。

在springmvc拦截器中拦截请求头,projectId,然后放到ThreadLocal里,然后后续的所有当前线程,和当前子线程都可以访问到当前的projectId。

然后在所有的mybatis拦截器中,拿到所有的表名,再根据当前的projectId,来判断是否需要拒绝访问。

代码

全局的holder; 使用TransmittableThreadLocal;

防止在service中使用线程池,导致ThreadLocal的值拿不到。


import com.alibaba.ttl.TransmittableThreadLocal;

/**
 * 全局ContextHolder
 * @author ZY
 * @date 2022/4/12 11:09
 */
public final class CommonContextHolder {
    /* projectId start */
    private static final ThreadLocal<Integer> PROJECT_HOLDER = new TransmittableThreadLocal<>();

    public static void setProjectId(int projectId) {
        PROJECT_HOLDER.set(projectId);
    }

    public static Integer getProjectId() {
        return PROJECT_HOLDER.get();
    }

    public static void clearProjectId() {
        PROJECT_HOLDER.remove();
    }
    /* projectId end */

    /*  client start */
    private static final ThreadLocal<String> CLIENT_HOLDER = new TransmittableThreadLocal<>();
    public static void setClient(String client) {
        CLIENT_HOLDER.set(client);
    }

    public static String getClient() {
        return CLIENT_HOLDER.get();
    }

    public static void clearClient() {
        CLIENT_HOLDER.remove();
    }
    /*  client end */
}

springmvc拦截器

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;

/**
 * 全局拦截器
 *
 * @date 2022/4/12 10:53
 */
@Component
public class CommonInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=utf-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Access-Control-Allow-Headers", "*");
        OutputStream outputStream = response.getOutputStream();

        String projectId = request.getHeader("project");

        // 记录当前请求的项目id
        Integer i = formatProjectId(projectId);
        if (i == null) {
            outputStream.write(JSONObject.toJSONString(ResponseEntity.invalidParams()).getBytes());
            return false;
        }
        CommonContextHolder.setProjectId(i);
        return true;
    }

    private Integer formatProjectId(String projectId) {
        if (StringUtils.isEmpty(projectId)) return null;
        try {
            return Integer.parseInt(projectId);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        CommonContextHolder.clearProjectId();
    }
}

mybatis拦截器

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 在发布模块的时候,锁定某些模块。
 * @date 2022/4/6 18:09
 */
@Component
@Intercepts({
        @Signature(type= Executor.class,method="update",args={MappedStatement.class,Object.class}),
        @Signature(type = Executor.class, method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
public class ModuleLockMybatisInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        if (!needVerify(ms)) {
            return invocation.proceed();
        }
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        String sql = boundSql.getSql();
        List<String> tableNameBySql = getTableNameBySql(sql);
        log.debug("解析sql:{} \n表名:{}", sql, tableNameBySql);
        if(isLock(tableNameBySql)){
            // 如果用Exception,mybatis获取不到msg
            throw new RuntimeException(ResponseCodeEnum.DENY_ACCESS_BY_RELEASING.getMsg());
        }
        return invocation.proceed();
    }

    private boolean needVerify(MappedStatement ms){
        Integer projectId = CommonContextHolder.getProjectId();
        if(projectId == null) return false;
        return ModuleLockContext.needVerify(projectId);
    }

    private boolean isLock(List<String> tableNameList){
        Integer projectId = CommonContextHolder.getProjectId();
        if(projectId == null) return false;
       log.debug(Thread.currentThread().getName() + "-------sql--project-holder::::" + projectId);
        return ModuleLockContext.isLock(projectId, tableNameList);
    }

    /**
     * 通过druid工具通过sql解析表名
     * @param sql
     * @return java.util.List<java.lang.String>
     * @author ZY
     * @date 2022/4/7 9:54
     */
    private static List<String> getTableNameBySql(String sql) {
        List<String> fromCache = SqlTableCache.getFromCache(sql);
        if(fromCache != null) return fromCache;

        String dbType = JdbcConstants.MYSQL;
        try {
            List<String> allTableNameList = new ArrayList<>();
            /*//格式化输出
            String sqlResult = SQLUtils.format(sql, dbType);
            log.debug("格式化sql={}"+sqlResult);*/
            List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
            if (CollectionUtils.isEmpty(stmtList)) {
                log.debug("stmtList为空无需获取");
                return Collections.emptyList();
            }
            for (SQLStatement sqlStatement : stmtList) {
                MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
                sqlStatement.accept(visitor);
                Map<TableStat.Name, TableStat> tables = visitor.getTables();
                log.debug("druid解析sql的结果集:{}",tables);
                Set<TableStat.Name> tableNameSet = tables.keySet();
                allTableNameList.addAll(tableNameSet.stream().map(TableStat.Name::getName).filter(StringUtils::isNotBlank).collect(Collectors.toList()));
            }
            log.debug("解析sql后的表名:{}",allTableNameList);
            SqlTableCache.addToCache(sql, allTableNameList);
            return allTableNameList;
        } catch (Exception e) {
            log.error("解析sql异常,sql=" + sql, e);
        }
        return Collections.emptyList();
    }

}

transmittable-thread-local-2.12.1-API文档-中文版.zip 赠送jar包:transmittable-thread-local-2.12.1.jar; 赠送原API文档:transmittable-thread-local-2.12.1-javadoc.jar; 赠送源代码:transmittable-thread-local-2.12.1-sources.jar; 赠送Maven依赖信息文件:transmittable-thread-local-2.12.1.pom; 包含翻译后的API文档:transmittable-thread-local-2.12.1-javadoc-API文档-中文(简体)版.zip; Maven坐标:com.alibaba:transmittable-thread-local:2.12.1; 标签:alibaba、transmittable、thread、local、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。 立即下载

相关推荐

如何在windows电脑上搭建Linux环境(手把手教安装虚拟机软件和使用云服务器)

手把手教学如何搭建Linux环境

纪宁的博客 1万+

Spring注解驱动开发-@Conditional

Spring注解驱动开发-@Conditional

m0_60742341的博客 772

四自由度SCARA机器人的运动学和动力学MATLAB建模与仿真

本文介绍了四自由度SCARA机器人的运动学和动力学建模,使用MATLAB进行了相应的建模与仿真。通过正运动学和动力学分析,可以了解机器人在不同状态下的运动特性和力学特性。这为后续的控制算法设计和性能优化打下了基础。希望本教程对学习SCARA机器人有帮助。

小蜗牛的珍贵百宝箱 3640

java Lock 源码分析

java 中的lock 先关的类路径:Lock 是个接口,源码:// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package java.util.concurrent.locks; import java.util.concurre...

lxlmycsdnfree的博客 2897

第28节:cesium 加载高程数据(含源码+视频)

cesium 加载高程(地形)数据

qq_29384639的博客 1741

家里摄像头录像存 NAS,空间爆满了怎么办?一招实现设定容量自动清理实现循环存储!

📷 家用摄像头录像久了会撑爆 NAS 吗?别担心,这篇文章教如何实现自动清理录像,释放 NAS 空间,避免爆盘🧹

XiaoqiangClub的博客 1882

Idea 使用技巧记录

1、配置 sys 输出快捷键 首先点击工具栏 File->Settings,搜索 Live… 新建一个分组,然后在该分组下添加快捷键。 因为已经配置了 sys 快捷键,这里配置 try 快捷键,过程一样。 此时, 在下面有一条红色警告语, 点击最下面的”Define”, 选择 Java。 点击 Apply ,然后点击 OK 退出。在 Java 文件中输入 sys 即可带出 Syst...

Herishwater的博客 2830

《大数据: Hive 交互命令和常用配置》

一、Hive 交互命令 1. 首先解决一个可能发生的问题 (log4 jar包冲突) 我这里解决方法就是删除一个,我删的是hive下的包 yexiang@hadoop1:<hive>$ hive SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/opt/soft/apache-hive-2.3.7-bin/lib/log4j-slf4j-impl-2.6.2.ja.

yexiang优快云的专栏 289

ThreadLocal 学习笔记

对 Threadlocal 理解 ThreadLocal 是为了解决线程间同步而创建的一个新的思路。简单来说就是每个线程都保存一个变量副本。 如果在 Thread 内部定义一个 field 变量,也可以解决这个问题。 这样就需要定义一个新的 Thread 类,来解决这个问题。每一次一个新的变量都需要这个 case,but,实际这个新的类,与 thread 本身并没有关系。 所以最好有一种方式,可以...

怪咖先森的博客 292

SQL的基本介绍

1.什么是数据库?2.什么是数据库管理系统?3.什么是 SQL?1.数据库是保存有组织的数据的容器,通常是一个文件或一组文件,可以将其看作电子化的文件柜。用户可以对文件中的数据执行新增、删除、更新、查询等操作。2.数据库管理系统是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户可以通过 DBMS 访问数据库中的数据,数据库管理员也可以通过 DBMS 进行数据库的维护工作。

shiwei0813的博客 2879

【系统架构设计师-2026上半年真题】案例分析-参考答案及详解(回忆版)

架构评估与设计:通过入侵检测系统的案例分析,阐述质量属性(性能/可用性/安全性/可修改性)的匹配方法,推荐管道-过滤器风格处理实时数据流,并设计8个核心构件实现数据采集到告警的全流程处理。 高并发优化方案: 分析悲观锁(排他锁/共享锁)与乐观锁的特性差异,提出乐观锁通过版本号/CAS实现,悲观锁适用于强一致性场景。 给出Redis优化三手段:分布式锁控制临界资源、Lua脚本保证原子操作、热点数据多级缓存,结合消息队列削峰填谷。 分层架构实践: 智

数据知道的博客 1686
上一篇: 批量csv转excel(VB宏)
下一篇: dubbo注册到公网ip
BigDevil_
博客等级 码龄9年 1818粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值