前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ThreadLocal之美!

ThreadLocal之美!

原创
作者头像
Joseph_青椒
修改于 2023-08-08 12:12:23
修改于 2023-08-08 12:12:23
25800
代码可运行
举报
文章被收录于专栏:java_josephjava_joseph
运行总次数:0
代码可运行

来吃透threadlocal!

ThreadLocal使用

使用场景

1:需要独享对象的非线程安全的对象

常用于例如SimpleDateFormatter这样非线程安全的工具类上,比如需要1000次用到这个工具类,想要不频繁的创建导致的开销,以及高效的避免线程安全问题,就可以用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @Author:Joseph
 * @bolg:https://li-huancheng.gitee.io/
 * @Package:threadLocal
 * @Project:bing-fa-demo
 * @name:ThreadLocalDateTest
 * @Date:2023-07-21 15:36
 * @Filename:ThreadLocalDateTest
 */
public class ThreadLocalDateTest {public static ExecutorService theadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) {for (int i = 0; i < 1000; i++) {
            int finalI = i;
            theadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalDateTest().date(finalI);
                    System.out.println(date);
                }
            });
        }
​
​
    }
    public String date(int seconds){//从1970:1。1.0.0 00:00:00    GMT计sh'j
        // 因为Data是毫秒单位,标识秒,得乘1000
        Date date = new Date(1000*seconds);
        //格式化
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd:hh:mm: ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        return dateFormat.format(date);}}
class ThreadSafeFormatter{
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd:hh:mm: ss");
        }
    };}

例如这个场景,如果不用ThreadLocal,避免多次创建对象开销,那么只能通过synchronized加锁来保证线程安全,但是对应的就是重量级锁的效率问题,那么通过ThreadLocal就能很轻松的完成这个需求,且效率是很快的

2:线程内需要保存全局变量来避免传参麻烦

对于用户信息的传递,比如在interceptor层中,想要传递用户信息 ,首先直接用static是不行的,因为对于不同的线程,都有自己不同的信息,那么可以用一个线程安全的map,比如currentHashMap.让不同的线程存入,用到的时候再去取,即使currentHashMap效率算高了,但是他也是通过AQS cas这些操作来保障的,肯定是影响性能的,那么就可以用ThreadLocal,拦截解密token之后,放到theadLocal,用的时候取就可以了!。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Slf4j
public class LoginInterceptor implements HandlerInterceptor {public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();
​
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
​
        String accessToken = request.getHeader("token");
        if(accessToken == null ){
            accessToken = request.getParameter("token");}if(StringUtils.isNotBlank(accessToken)){
            //不为空
            Claims claims = JWTUtil.checkJWT(accessToken);
            if(claims == null){
                 //未登录
                CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));
                return false;}
            long userId= Long.valueOf(claims.get("id").toString());
            String headImg = (String)claims.get("head_img");
            String name = (String)claims.get("name");
            String mail = (String) claims.get("mail");
​
            LoginUser loginUser = LoginUser.builder()
                    .headImg(headImg)
                    .name(name)
                    .id(userId)
                    .mail(mail).build();
​
​
            //通过attribute传递用户信息
            //request.setAttribute("loginUser",loginUser);//通过threadlocal传递用户登录信息
            threadLocal.set(loginUser);return true;
​
​
​
        }
        CommonUtil.sendJsonMessage(response,JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN));return false;
    }
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        threadLocal.remove();
    }
}

这里可以看到使用了,很简单,拦截到set进去就行了,但是注意afterCompletion中的remove(),埋个伏笔,

需要使用的时候

拿出来就好了

这里总结一下ThreadLocal的作用

第一个就是让对象在线程间隔离,每个线程都可以持有自己独立的对象。简单的说,就是同个对象,在不同线程之间是不一样的,并没有重复的创建与销毁,只是创建了线程的数量而已,比如上面1000次,10个线程,那么仅仅创建了10个,对应的就是场景一,对于非线程安全的对象,要多次使用,就可以用theadLocal

第二个就是对于不同的线程(用户),都可以轻松的获取对象,来达到避免传参麻烦

注意事项

有没有发现,场景一,只用工具类的时候,用的initialValue而场景二用的set方法,原因在于,场景一创建的时间是我们可控的,代码运行到这里,进行初始化就可以创建好,就用InitialValue,set的话是不可控的,加载完也是空的,拦截器拦截到,再set进去,所以就会用set,这就是threadLocal使用的一些注意事项。

使用ThreadLocal的好处

1,线程安全

2.不用加锁,效率高

3更好的利用内存,节省开销,比如1000次那个场景,10个线程,对应只消耗10次对象的创建,不同的区分发生在内存的执行上,内存本身就要消耗。

4,不用层层传参

原理、源码

使用会了,现在搞源码!!

理清threadLocal,Thread、threadLocalMap三者的关系

我们知道,这个threadLocal的创建是以线程为单位的,每个线程持有一个对象,但是为啥要有threadLocalMap呢?

这是因为一个程序中,不一定只有一个threadLocal,我们再使用的时候,有可能用了一个拦截器的set,也用了一个工具类,就是使用中的两种情况,这就需要两个threadlocal

这是Thread中的,thread持有thredLoalMap,并命名为threadLocals对象

重要方法

第一个就是initalValue了,这个方法提供初始化,但是是延迟加载的,在get的时候才会触发

现在我们看一下这个方法,进入ThreadLocal类中

可以看到这个是return null,的 我们重写,才会有值

延迟加载,看一下get方法

这里的value就是我们重写的initialValue,不重写是null

当场景2,就是用set的时候,这个initialValue是不会取执行的

retrun的是我们set近ThreadLocalMap中的值

initaialValue方法还需要我们注意的是,只会在第一次调用get会触发,后面就不会触发了,会和set一样,进入if(map!=null)的逻辑,值得注意的是,当我们remove之后,threadLoalMap又空了,就可以触发了,

set方法

这个就是为线程设置一个新的值,

这个就比较简单了,拿到当前线程,再取找thradLocalMap找不到就去创建,找到就set进去

值得注意的是:threadLocal类中只是一些操作的方法,具体的保存,都是再threadLocalMap中的,也就是说,都是保存再线程中的,threadLocalMap中,key是threadLocal,value是set进去的值!!

get

就是拿到线程对应的value,threadLocalMap是以key-value为键值对的集合,这里指的就是对应threadLocal中的value

代码是这样的

就是从拿到threadLocalMap中存储我这个threadLocal的value

这里的map是thread类中的threadLocalmap,map中通过threadLocal作为key,放进去找到对应的value

remove方法就是移除value

这个就很简单了

都是一个套路。就是通过当前线程,拿到threadLocalMap,然后讲threadLocal作为key去remove数据

注意这个remove的key是当前的threadLocaL,并不是所有的threadLocal的value

threadLoalMap

这里有必要着重分析一下这个类,是整个threadLocal实现的核心

刚才我讲的,多多少少提到了

threadLocalMap指的就是thread类中的threadLocals,类型是ThreadLocal.thredLocalMap,也就是说,这个threadLoaclMap定义是在threadLocal中的,但是threadLocalMap和threadLocal使用是被thread类持有的,thread中持有threadLocalMap,threadLocalMap的key是threadLocal,

这个关系比较混乱,我们可以把它理解为一个hashMap但是略有区别

它发生hash冲突不是用拉链法,而是用线性探测法,冲突了,就在table中遍历往下找

通过源码分析

我们可以理解,initialValue和set都是通过map.set放value的,只不过initialVlaue起点是get方法,map为空,调用initial方法的罢了

内存泄漏

很多人都不容为什么拦截器的最后,要remove一下,Jvm垃圾回收器不是可以回收吗?

这里先理清一下内存泄漏和内存溢出的区别

内存泄漏指的是:对象不使用了,应该被回收,但是没被回收

内存溢出指的是:就是申请的内存不够用,造成outofMemory

再讲一下:四大引用

强软弱虚

强引用,Reference

这个是很普遍的,

比如String s=''ss'',在内存不足的时候,jvm宁愿抛出内存溢出oom,也不会回收这个对象,只有在这个jvm进程结束才会回收

软引用:Software

这里就是再降低一些要求,内存资源够的时候,触发GC也不会回收,但是内存不够了,就会回收,指的就是有用,但不是非要不可的地步

弱引用:WeakReference

用完就没用了的东西,用过了,gc的时候一定会被回收

虚引用就用的很少了,目的是让系统知道这个对象被回收

  • 总结

引用类型

被垃圾回收时刻

用途

生存时间

强引用

从来不会

对象的一般状态

JVM停止运行时终止

软引用

在内存不足时

对象简单,缓存,文件缓存,图片缓存

内存不足时终止

弱引用

在gc垃圾回收时

对象简单,缓存,文件缓存,图片缓存

gc运行后终止

虚引用

任何时候都可能被垃圾回收器回收

基本不写,虚拟机使用, 用来跟踪对象被垃圾回收器回收的活动

未知

我们看一下threadLocalMap

注意这里k的赋值,并不是直接赋值,而是super了一个key,也就是虚引用,这个再发生垃圾回收是可以回收的,

但是这个value是强引用

我们知道,线程执行完就会消失,那么

Thread->ThreadLocalMap->Entry(key=null)->value

这个调用链,发生gc之后,key消失了,但是value还在,当线程执行完毕后,value会随着Threa生命的终止,也会丢失调用链路,可达性分析算法,大家可以了解,这样也是可以回收的

但是当我们用线程池的时候,一个线程是重复利用的,比如拦截器这里,我这个线程处理完一个用户,再处理下一个,那么这里的threadLocal和value是会很多的,value强引用,是不会被回收的,因为线程池服用线程,进程不会结束,

jdk考虑到了这个问题

remove\resize\set方法,会扫描为null的,并设置为null

比如这里,k为null,就会让value=null,注释也写了,help the gc,

但是当一个threadLocal不再被使用,那么这些方法也无法调用,

所有就需要我们再threadLocal结束前,要去提前remove

现在知道这个伏笔了吧!

内存泄漏是threadLocal的一个坑,同样还有

空指针异常

可能听过这样一句话,threadLocal用的时候要set,不然get的时候会报空指针异常 这个问题,简直不要太离谱。我们知道,initialValue方法,即使不调用,也是会有一个默认的null,的,顶多get到一个null,那么为什么有人会遇到get下,报空指针异常问题呢?这下,你就得听我好好唠唠了,不然真开发中遇到,排错很麻烦的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
**
 * @Author:Joseph
 * @bolg:https://li-huancheng.gitee.io/
 * @Package:threadLocal
 * @Project:bing-fa-demo
 * @name:ThreadLoaclNullPointer
 * @Date:2023-07-23 11:59
 * @Filename:ThreadLoaclNullPointer
 */
public class ThreadLoaclNullPointer {
​
    ThreadLocal<Long> longThreadLocal= new ThreadLocal<Long>();
    public void set(){
        longThreadLocal.set(Thread.currentThread().getId());
    }
    public long get(){
        return longThreadLocal.get();
    }public static void main(String[] args) {
        ThreadLoaclNullPointer threadLoaclNullPointer = new ThreadLoaclNullPointer();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLoaclNullPointer.get());
            }
        });
        thread.start();
    }
}

没错,报错了!

你能发现那里的错误吗,先别看答案,自己copy代码debug一下再看答案,

答案就是

自动拆箱,出现了空指针异常,拆箱的时候,null是无法拆箱的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//修改为Long
public Long get(){
        return longThreadLocal.get();
    }

这样就正常了,这是我们需要注意的地方

错误样例

threadLocal使用,还有注意点

static对象,当我们set的时候,还是共享的,因为static本身类变量就是共享的,我们想用直接类.static对象就可以了,不需要放到threadLocal中

另外没必要用theadLocal就不要用了,比如在场景一中,只需要用两三次的工具类,直接new三次对象就行了

Spring中的应用

spring中用到threadLocal也是很多的,XXXContextHolder

比如RequestContextHolder

进入

只是对threadLocal做了层小小的包装,取名字而已

比如这个方法,就是把请求参数返回,也是从threadLocal中去取

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
1 条评论
热度
最新
博主你好,点击按钮查看刷新的工具是什么
博主你好,点击按钮查看刷新的工具是什么
回复回复点赞举报
推荐阅读
[- Flutter 数据&状态篇 -] InheritedWidget
一个界面是由众多组件拼组而成。经常需要将一个组件进行封装,但此时有一个问题,如何让多个组件去共享一些值。 比如下面在一个_State中使用了WidgetA组件,传入_incrementCount
张风捷特烈
2020/04/30
3720
[- Flutter 数据&状态篇 -]  InheritedWidget
flutter系列之:widgets,构成flutter的基石
flutter中所有的组件都是由widgets组成的,flutter中有各种各样的widgets,这些widgets构成了flutter这个大厦。
程序那些事
2022/08/18
7190
Riverpod - flutter 状态管理的应用
Riverpod 是 Flutter 下知名度较高的状态管理依赖,同样出自 Provider 的开发者 rrousselGit 之手。
xcsoft
2024/07/31
2781
Flutter中的状态管理
Flutter作为出自Google的一个跨平台(iOS,Android)应用开发方案。布局方式上和React或者说React Native非常相似——组件(Widget)化。写起来非常的高效,却有着React Native所不具有的优势: 一套代码到处运行,原生渲染,原生调用,不需要像RN需要桥接。
小刀c
2022/08/16
1.3K0
Flutter中的状态管理
Flutter Lesson 3:Flutter组件(widget)前篇
介绍完Flutter开发环境的搭建以及Dart基础语法,我们就可以着手进行开发了。一般我们开始学习一门技术或者是一门语言的时候,都会写一个Hello World的Demo。所以,撸起袖子开始干。不过在职之前,我们先来看看Flutter项目的默认文件以及目录结构。
踏浪
2019/07/31
9040
Flutter Lesson 3:Flutter组件(widget)前篇
小荷才露尖尖角,和Flutter应用说你好
当按钮点击,就调用这个函数,改变状态会使用setState方法,这个和React的类组件汇总改变状态的方式很像
用户6256742
2024/06/13
1110
在 Flutter 移动应用程序中创建一个列表
Flutter 是一个流行的开源工具包,它可用于构建跨平台的应用。在文章《用 Flutter 创建移动应用》中,我已经向大家展示了如何在 Linux 中安装 Flutter 并创建你的第一个应用。而这篇文章,我将向你展示如何在你的应用中添加一个列表,点击每一个列表项可以打开一个新的界面。这是移动应用的一种常见设计方法,你可能以前见过的,下面有一个截图,能帮助你对它有一个更直观的了解:
用户1880875
2021/09/06
3.4K0
【Flutter 工程】003-钩子函数:Flutter Hooks
Hooks 是 React 框架中引入的一项特性,用来分离状态逻辑和视图逻辑。如今,这个概念已经不仅限于 React,其他前端框架也在学习和借鉴。在 Flutter 开发中,业务逻辑和视图逻辑的耦合一直是一个比较突出的痛点,这也是各大前端框架常遇到的一个共性难题。为了解决这个问题,前端社区提出了许多方案,如MVP、MVVM、React 的Mixin、高阶组件(HOC),以及Hooks。在Flutter中,开发者可能对Mixin比较熟悉。但是,Mixin的应用也存在一定的局限性:
訾博ZiBo
2025/01/06
1430
【Flutter 工程】003-钩子函数:Flutter Hooks
01-HelloFlutter
https://gitee.com/andli/hello-flutter.git
专注APP开发
2019/11/07
3800
Flutter的两种本地存储方式之 SharedPreferences(1)
缓存少量的键值对信息(比如记录用户是否阅读了公告,或是简单的计数),可以使用 SharedPreferences。
徐建国
2021/08/06
1.4K0
动手编写你的第一个 Flutter 应用
我将带领大家尝试编写一个 Flutter 应用,感受一下 Flutter 开发的语法特点和运行效率。
CSDN技术头条
2020/02/26
1K0
[ - Flutter 状态篇 redux - ] StoreConnector还是StoreBuilder,让distinct把好关
很多天没发文了,今天翻翻源码,发现解决一个困扰我的问题:redux中的StoreConnector还是StoreBuilder似乎都可以定点刷新,控制粒度。那它们有什么区别呢?在官方样例中基本都用St
张风捷特烈
2020/04/30
1K0
[ - Flutter 状态篇 redux - ] StoreConnector还是StoreBuilder,让distinct把好关
Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit)
flutter的生命周期其实有两种:StatefulWidget和StatelessWidget。
BennuCTech
2021/12/10
1.7K0
Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit)
Flutter入门三部曲(2) - 界面开发基础
上一节我们熟悉了初始化后的flutter的界面。这一节,我们就来重点了解一下这部分的内容。
deep_sadness
2018/08/17
2.6K0
Flutter入门三部曲(2) - 界面开发基础
推荐一种简单的在Flutter中分离View与Model的方法
我们在做Flutter开发的时候主要会在State中加入很多自己的业务逻辑,例如网络请求,数据处理等等,如果你的业务逻辑比较复杂的话会面对着一个越来越膨胀的State。代码的可读性下降,日后维护也越来越困难。这和我们在开发Android的时候遇到巨无霸Activity是同样的问题。解决办法就是分层解耦。Android从MVC进化到MVP/MVVM。Flutter 也有开发者把MVP引入到Flutter来解决这个问题。这里我们来看另一种比较简单的方法。
HowHardCanItBe
2020/09/15
1.5K0
【Flutter】348- 写给前端工程师的 Flutter 教程
| 导语 最爱折腾的就是前端工程师了,从 jQuery 折腾到 AngularJs,再折腾到 Vue、React。最爱跨屏的也是前端工程师,从 phonegap,折腾到 React Native,这不又折腾到了 Flutter。
pingan8787
2019/09/17
1.1K0
【Flutter】348- 写给前端工程师的 Flutter 教程
Flutter 组件 | ValueListenableBuilder 局部刷新小能手
我们对初始项目非常熟悉,在 _MyHomePageState 中,通过点击按钮将状态量 _counter 自加,在使用 setState 让当前 State 类持有的 Element 进行更新。作为初学者来说,这很方便,也很容易理解。但对于已入门的人来说,这样的 setState 显然是有失优雅的。
张风捷特烈
2021/01/04
8.5K0
Flutter 组件 |  ValueListenableBuilder 局部刷新小能手
原来Flutter代码是这样运行在原生系统的!快来了解Flutter标准模板,感受原生系统中Flutter的魅力!
通过Android Studio创建的Flutter应用模板,了解Flutter项目结构,分析Flutter工程与原生Android和iOS工程有哪些联系,体验一个有着基本功能的Flutter应用是如何运转的,从而加深你对构建Flutter应用的关键概念和技术的理解。
JavaEdge
2023/07/09
6030
原来Flutter代码是这样运行在原生系统的!快来了解Flutter标准模板,感受原生系统中Flutter的魅力!
【Flutter】自定义 Flutter 组件 ( 创建自定义 StatelessWidget、StatefulWidget 组件 | 调用自定义组件 )
Flutter 开发中 , 组件可以是一个 Button 按钮 , Text 文本 , 也可以是封装好的一大块区域 ; 组件由 Widget 组成 ;
韩曙亮
2023/03/29
2K0
【Flutter】自定义 Flutter 组件 ( 创建自定义 StatelessWidget、StatefulWidget 组件 | 调用自定义组件 )
简单了解Flutter
距离Flutter正式版出来已经有很长的时间了,目前大家对于Flutter的呼声也是很高,就算是平时不了解移动开发的朋友们也开始好奇Flutter究竟是个什么东西。就连我朋友的老板都开始问,公司产品能不能换成Flutter来开发?
写代码的阿宗
2020/08/24
9410
简单了解Flutter
推荐阅读
相关推荐
[- Flutter 数据&状态篇 -] InheritedWidget
更多 >
LV.0
腾讯客户端
目录
  • ThreadLocal使用
    • 使用场景
    • 注意事项
    • 使用ThreadLocal的好处
  • 原理、源码
    • 理清threadLocal,Thread、threadLocalMap三者的关系
    • 重要方法
    • threadLoalMap
  • 内存泄漏
  • 空指针异常
  • 错误样例
  • Spring中的应用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档