阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

ThreadLocal的正确使用与原理

284次阅读
没有评论

共计 4033 个字符,预计需要花费 11 分钟才能阅读完成。

ThreadLocal 是什么

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

ThreadLocal 原理

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

ThreadLocal 内存泄漏问题

线程的属性值 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用, 而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样的话,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
因此针对这种情况,我们有两种原则:
ThreadLocal 申明为 private static final。JDK 建议 ThreadLocal 定义为 private static,这样 ThreadLocal 的弱引用问题则不存在了。private 与 final 尽可能不让他人修改变更引用。static 表示为类属性,只有在程序结束才会被回收。ThreadLocal 使用后务必调用 remove 方法。
最简单有效的方法是使用后将其移除。

关于 InheritableThreadLocal

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

ThreadLocal 使用

public class ThreadLocalTest {

    // 第一种初始化方式
    /**
     * 声明为 static 是让 ThreadLocal 实例随着程序的结束才结束,这样才不会让 GC 回收了
     * 声明为 final 是让 ThreadLocal 实例引用不会被替换,这样子也不会因为被替换导致被 GC 回收
     * 这两个声明都是为了避免作为 key 的 ThreadLocal 对象没有外部强引用而导致被 GC 回收,从而导致内存泄漏的问题,因为 ThreadLocalMap 中的 ThreadLocal
     * 对象作为 key 是弱引用,会被 GC 回收。*/
    private static final ThreadLocal threadLocalStr = ThreadLocal.withInitial(() -> "fresh");

    private static AtomicInteger intGen = new AtomicInteger(0);
    // 第二种初始化方式
    private static final ThreadLocal threadLocalInt = new ThreadLocal() {
        @Override
        public Integer initialValue() {return intGen.incrementAndGet();
        }
    };

    public static void main(String[] args) throws InterruptedException {ArrayList threads = new ArrayList();
        for (int i = 0; i  {
                try {System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                    TimeUnit.SECONDS.sleep(5);
                    threadLocalStr.set("bojack horseman" + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                } catch (InterruptedException e) {e.printStackTrace();
                } finally {threadLocalInt.remove();
                    threadLocalStr.remove();}
            });
            t.start();
            threads.add(t);
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(threads);
        System.out.println(threadLocalStr);
        System.out.println(threadLocalInt);
    }
    /**
     * Thread-0   1
     * Thread-1   2
     * Thread-0   fresh
     * Thread-1   fresh
     * [Thread[Thread-0,5,main], Thread[Thread-1,5,main]]
     * java.lang.ThreadLocal$SuppliedThreadLocal@1ef7fe8e
     * cn.vv.schedule.test.ThreadLocalTest$1@6f79caec
     * Thread-1   2
     * Thread-1   bojack horseman2
     * Thread-0   1
     * Thread-0   bojack horseman1
     */

}

InheritableThreadLocal 使用

public class InheritableThreadLocalTest {

    // 第一种初始化方式
    private static final InheritableThreadLocal threadLocalStr = new InheritableThreadLocal() {
        @Override
        public String initialValue() {return "fresh";}
    };
    private static AtomicInteger intGen = new AtomicInteger(0);
    // 第二种初始化方式
    private static final ThreadLocal threadLocalInt = new ThreadLocal() {
        @Override
        public Integer initialValue() {return intGen.incrementAndGet();
        }
    };

    public static void main(String[] args) throws InterruptedException {
        // 如果是 InheritableThreadLocal,则父线程创建的所有子线程都会复制一份父线程的线程变量,而不是去初始化一份线程变量
        threadLocalStr.set("main");
        ArrayList threads = new ArrayList();
        for (int i = 0; i  {
                try {System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                    TimeUnit.SECONDS.sleep(5);
                    // 子线程可以自由地改变自己的本地变量
                    threadLocalStr.set("bojack horseman" + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                } catch (InterruptedException e) {e.printStackTrace();
                } finally {threadLocalInt.remove();
                    threadLocalStr.remove();}
            });
            t.start();
            threads.add(t);
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
    }
    /**
     * Thread-0   2
     * Thread-1   1
     * Thread-0   main
     * Thread-1   main
     * main   main
     * Thread-0   2
     * Thread-0   bojack horseman2
     * Thread-1   1
     * Thread-1   bojack horseman1
     */

}

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-25发表,共计4033字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19348
评论数
4
阅读量
7804453
文章搜索
热门文章
开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南

开发者必备神器:阿里云 Qoder CLI 全面解析与上手指南 大家好,我是星哥。之前介绍了腾讯云的 Code...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板1Panel:小白轻松构建Web服务与面板加固指南

云服务器部署服务器面板 1Panel:小白轻松构建 Web 服务与面板加固指南 哈喽,我是星哥,经常有人问我不...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
CSDN,你是老太太喝粥——无齿下流!

CSDN,你是老太太喝粥——无齿下流!

CSDN,你是老太太喝粥——无齿下流! 大家好,我是星哥,今天才思枯竭,不写技术文章了!来吐槽一下 CSDN。...
从“纸堆”到“电子化”文档:用这个开源系统打造你的智能文档管理系统

从“纸堆”到“电子化”文档:用这个开源系统打造你的智能文档管理系统

从“纸堆”到“电子化”文档:用这个开源系统打造你的智能文档管理系统 大家好,我是星哥。公司的项目文档存了一堆 ...
星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

星哥带你玩飞牛NAS-16:飞牛云NAS换桌面,fndesk图标管理神器上线!

  星哥带你玩飞牛 NAS-16:飞牛云 NAS 换桌面,fndesk 图标管理神器上线! 引言 哈...
三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

  三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Andr...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择

星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择

星哥带你玩飞牛 NAS-12:开源笔记的进化之路,效率玩家的新选择 前言 如何高效管理知识与笔记,已经成为技术...
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
【开源神器】微信公众号内容单篇、批量下载软件

【开源神器】微信公众号内容单篇、批量下载软件

【开源神器】微信公众号内容单篇、批量下载软件 大家好,我是星哥,很多人都希望能高效地保存微信公众号的文章,用于...
每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站-手机博物馆-CHAZ 3D Experience

每天一个好玩的网站 - 手机博物馆 -CHAZ 3D Experience 一句话介绍:一个用 3D 方式重温...
三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Android 的最优解?

  三大开源投屏神器横评:QtScrcpy、scrcpy、escrcpy 谁才是跨平台控制 Andr...