ThreadLocal的正确使用与原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal的正确使用与原理相关的知识,希望对你有一定的参考价值。

参考技术A

ThreadLocal是线程Thread中属性threadLocals即ThreadLocal.ThreadLocalMap的管理者,ThreadLocal用于给每个线程操作自己线程的本地变量,通过线程私有从而保证线程安全性。

拿 get() 方法来说,线程的本地变量是存放在线程实例的属性ThreadLocalMap上的,ThreadLocalMap本质上就是一个HashMap,ThreadLocal只是一个管理者,当我们的线程需要拿到自己的本地变量时,我们直接调用ThreadLocal去get本地变量即可。
因为 get() 方法底层会先获取到当前线程,然后通过当前线程拿到他的属性值ThreadLocalMap,如果ThreadLocalMap为空,则会调用ThreadLocal的初始化方法拿到初始值返回,如果不为空,则会拿该ThreadLocal作为key去获取该线程下的ThreadLocalMap里对应的value值。

线程的属性值ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而value 不会被清理掉。这样的话,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
因此针对这种情况,我们有两种原则:

InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每个线程拥有它自己的值,与ThreadLocal不同的是, InheritableThreadLocal允许一个线程以及该线程创建的所有子线程都可以访问它保存的值

Java--ThreadLocal原理与使用

ThreadLocal保证线程安全:

ThreadLocal内部持有ThreadLocalMap对象,线程内部单独创建副本,来保证数据隔离,但是由于ThreadLocalMap中key为弱引用,GC自动回收,但是value如果为强引用,就没法回收,就会造成内存泄露(除非线程退出)

关于垃圾回收(自动回收堆中没引用的对象空间):https://www.jianshu.com/p/23f8249886c6

参考链接:https://www.cnblogs.com/jalon/p/14819372.html



    /*
    * 线程安全:
    * 1.synchronized修饰
    * 2.ThreadLocal修饰--数据隔离
    *
    * */
    private ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            // 这里会输出10次,分别是每个线程的id
            System.out.println(Thread.currentThread().getId());
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
    public void parse2(String dateString){
        try {
            System.out.println(threadLocal.get().parse(dateString));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }


    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    public void parse(String dateString){
        try {
            synchronized (simpleDateFormat){
                System.out.println(simpleDateFormat.parse(dateString));
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    @Test
    void testThreadLocal(){
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 300; i++) {
            service.submit(() -> {
//               parse("2021-05-30");
                parse2("2021-05-30");
            });
        }
    }

以上是关于ThreadLocal的正确使用与原理的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal的原理及用法

ThreadLocal 原理解析

用ThreadLocal类实现线程安全的正确姿势

java笔记java中ThreadLocal的原理和使用

ThreadLocal 原理解析(并不能解决多线程共享数据安全问题)

ThreadLocal 工作原理部分源码分析