专栏首页JVMGCJava线程封闭
原创

Java线程封闭

把对象封装到一个线程里,只有一个线程可以看到该对象,那么就算这个对象不是线程安全的,也不会出现任何线程问题,因为它只能在一个线程中被访问。

  • Ad-hoc线程封闭:程序控制实现,非常脆弱,最糟糕,忽略。
  • 堆栈封闭:简单的说就是局部变量,无并发问题。多线程访问同一个方法时,方法中的局部变量会被拷贝一份到线程栈中。方法的局部变量不是被多线程共享的,不会出现线程安全问题,能用局部变量就不要用全局变量,全局变量容易发生并发问题,注意全局变量不是全局常量。
  • ThreadLocal线程封闭:Java中提供一个ThreadLocal类来实现线程封闭,这个类使线程中的某个值与保存值的对象关联起来

ThreadLocal

ThreadLocal类提供的方法

image-20210105141906919

核心的五个操作:创建,创建并赋初始值,赋值,取值,删除

  • 创建:
private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;
  • 创建并赋初始值
private final static ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "入门小站";
        }
};
  • 赋值
threadLocal.set("入门小站");
  • 取值
threadLocal.get();
  • 删除
threadLocal.remove();

实现原理

首先ThreadLocal是一个泛型类,保证可以接受任何类型的对象。

一个线程内可以存在多个ThreadLocal,ThreadLocal内部维护了一个Map,这个Map不是HashMap,而是ThreadLocal实现的一个ThreadLocalMap的静态内部类。我们使用的get(),set()方法其实是调用了这个ThreadLocalMap类对应的get(),set()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

调用ThreadLocal的set方法时,先获取当前的线程Thread t = Thread.currentThread();,然后获取当前线程维护的ThreadLocalMap。如果ThreadLocalMap不存在则初始化。

ThreadLocalMapmap.set(this, value);第一个参数是this,this指的是当前的ThreadLocal,就是上面代码里面的threadLocal变量。

最终的变量是放在当前线程的ThreadLocalMap中,并不是存在ThreadLocal上,ThreadLocal可以理解成传递关系的。

内存泄漏问题

image-20210105172630930

ThreadLocalMap中使用的keyThreadLocal的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

所以ThreadLocal没有被强引用的情况下,在垃圾回收的时候会被清理掉,但是value却是强引用,不会被清理,这样的话就出出现keynullvalue

ThreadLocalMap实现中已经考虑了这个情况,在调用set,get,remove方法的时候会清理掉keynull的记录。如果出现了内存泄漏,那就是说在keynull后,没有手动调用remove方法,并且之后也不再调用set,get,remove方法。

内存泄漏解决方案

将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性

在ThreadLocal类中,还包含了一个static修饰的AtomicInteger(əˈtɒmɪk提供原子操作的Integer类)成员变量(即类变量)和一个static final 修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。

ThreadLocal应用

场景一

Web项目公共参数从controller层传递到service层,再从service层传递到mapper层,或者从service层传递到其他的工具类当中。为了避免参数复杂的传递,在controller中将已经封装好的参数放入ThreadLocal中,在其他层调用时直接通过ThreadLocal对象获取。在方法结束时,定义拦截器(HandlerInterceptorAdapter)(或者Filter)进行ThreadLocal的remove方法。

场景二

在需要登录的系统中用户信息常常存在Sessiontoken。比如我们要从Session中获取用户信息需要在接口参数中加上HttpServletRequest对象,然后调用 getSession方法,且每一个需要用户信息的接口都要加上这个参数,才能获取Session,比较麻烦。

这个时候我们就可以用ThreadLocal,在拦截器(HandlerInterceptorAdapter)(或者Filter)中解析获取用户信息,然后保存到ThreadLocal,业务逻辑直接在ThreadLocal中获取就可以了。

wx.jpg

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java Concurrent 线程封闭

    并发能够帮助我们完成资源的高效利用,业务逻辑的解耦。但是同时并发也带来了一些问题,比如说数据的并发安全问题。很多情况下,我们是希望能够并行的做一些事情,但是资源...

    邹志全
  • java并发之线程封闭(二)

    转载自 https://blog.csdn.net/maosijunzi/article/details/18561107

    allsmallpig
  • JAVA线程之ThreadLocal与栈封闭(六)

    PS:这次说了线程封闭的概念,其实很容易理解只要知道在ThreadLocal是JVM内部维护了一个Map就可以了。栈封闭没有纤细概述,跟局部变量是一个概念。

    IT架构圈
  • Java并发编程:线程封闭和ThreadLocal详解

    什么是线程封闭 当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了。这种技术称为线程封...

    方志朋
  • 线程封闭和实例封闭

    SuperHeroes
  • Java并发编程之线程封闭

    当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了。这种技术称为线程封闭。在Java语...

    技术训练营
  • JAVA高逼格面试:线程封闭

    码农的世界从来不缺乏名词。如果没有,我们就强行弄上几个。这些名词有垂直领域的知识缩写,也有水平领域的抽象划分。有的行云流水无比顺畅,有的晦涩难懂如便秘。

    xjjdog
  • 线程的通信和封闭

    要想实现线程之间的协同, 如: 线程先后执行顺序, 获取某个线程的执行结果等, 涉及线程之间的相互通信, 分为下面四类

    CodingDiray
  • 并发编程-11线程安全策略之线程封闭

    在上篇博文并发编程-10线程安全策略之不可变对象 ,我们通过介绍使用线程安全的不可变对象可以保证线程安全。

    小小工匠
  • Java 线程如何正确关闭

    heidsoft
  • Java 多线程处理任务的封装

    最近公司项目很多地方使用多线程处理一些任务,逻辑代码和java多线程处理代码混合在一起,造成代码的可读性超级差,现在把Java多线程相关的处理抽出来,方面代码中...

    哲洛不闹
  • Java项目实践,CountDownLatch实现多线程闭锁

    本文主要介绍Java多线程并发中闭锁(Latch)的基本概念、原理、实例代码、应用场景,通过学习,可以掌握多线程并发时闭锁(Latch)的使用方法。

    用户1289394
  • 解决python父线程关闭后子线程不关闭问题

    我们都知道,python可以通过threading module来创建新的线程,然而在创建线程的线程(父线程)关闭之后,相应的子线程可能却没有关闭,这可能是因为...

    砸漏
  • 并发实战 之「 对象的共享及组合」

    我们曾说过,要编写正确的并发程序,关键问题在于:在访问共享的可变状态时需要进行正常的管理。

    CG国斌
  • java 线程池使用后到底要不要关闭

    最近在开发中用到了java的线程池,然后就很疑惑这个线程池到底要不要手动关闭,感觉是要关闭的,但是没人强调线程池用完要关闭。so今天来试验下到底线程池用完要不要...

    用户2038589
  • java中产生死锁的原因及如何避免

    转载自 https://blog.csdn.net/m0_38126177/article/details/78587845

    allsmallpig
  • java并发编程实战笔记(部分实战未看,老旧章节跳过)

    终于把这本经典的Java并发书看完了,虽然之前看的Thinking in Java和Effective Java里面都有并发的章节,但是这本书讲的更加深入,并...

    何时夕
  • [并发编程] - Executor框架#ThreadPoolExecutor源码解读01

    JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有一个对应的线程 。

    小小工匠
  • 【小家Java】自定义的线程池需要关闭吗?(局部变量Executors线程池一定要手动关闭)

    我之前看到很多同事写代码,为了提高效率,采用多线程去优化。由为了提高多线程的性能,用到了线程池。

    YourBatman

扫码关注云+社区

领取腾讯云代金券