文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中ThreadLocal类怎么使用

2023-06-29 21:55

关注

这篇文章主要介绍“Java中ThreadLocal类怎么使用”,在日常操作中,相信很多人在Java中ThreadLocal类怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中ThreadLocal类怎么使用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Threadlocal有什么用:

简单的说就是,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)。如下图:

Java中ThreadLocal类怎么使用

ThreadLocal使用实例

API介绍

在使用Threadlocal之前我们先看以下它的API:

Java中ThreadLocal类怎么使用

ThreadLocal类的API非常的简单,在这里比较重要的就是get()、set()、remove(),set用于赋值操作,get用于获取变量的值,remove就是删除当前变量的值.需要注意的是initialValue方法会在第一次调用时被触发,用于初始化当前变量值,默认情况下initialValue返回的是null。

ThreadLocal的使用

说完了ThreadLocal类的API了,那我们就来动手实践一下了,来理解前面的那句话:一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)

public class ThreadLocalTest {    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {// 重写这个方法,可以修改“线程变量”的初始值,默认是null        @Override        protected Integer initialValue() {            return 0;        }    };    public static void main(String[] args) throws InterruptedException {        //一号线程        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("一号线程set前:" + threadLocal.get());                threadLocal.set(1);                System.out.println("一号线程set后:" + threadLocal.get());            }        }).start();        //二号线程        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("二号线程set前:" + threadLocal.get());                threadLocal.set(2);                System.out.println("二号线程set后:" + threadLocal.get());            }        }).start();        //主线程睡1s        Thread.sleep(1000);        //主线程        System.out.println("主线程的threadlocal值:" + threadLocal.get());    }}

稍微解释一下上面的代码:

每一个ThreadLocal实例就类似于一个变量名,不同的ThreadLocal实例就是不同的变量名,它们内部会存有一个值(暂时这么理解)在后面的描述中所说的“ThreadLocal变量或者是线程变量”代表的就是ThreadLocal类的实例。

在类中创建了一个静态的 “ThreadLocal变量”,在主线程中创建两个线程,在这两个线程中分别设置ThreadLocal变量为1和2。然后等待一号和二号线程执行完毕后,在主线程中查看ThreadLocal变量的值。

程序结果及分析⌛

Java中ThreadLocal类怎么使用

程序结果重点看的是主线程输出的是0,如果是一个普通变量,在一号线程和二号线程中将普通变量设置为1和2,那么在一二号线程执行完毕后在打印这个变量,输出的值肯定是1或者2(到底输出哪一个由操作系统的线程调度逻辑有关)。但使用ThreadLocal变量通过两个线程赋值后,在主线程程中输出的却是初始值0。在这也就是为什么“一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的”,每个线程都只能看到自己线程的值,这也就是 ThreadLocal的核心作用:实现线程范围的局部变量。

Threadlocal 的源码分析

原理

每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。 这句话刚看可能不是很懂,下面我们一起看完源码就明白了。

前面我们的理解是所有的常量值或者是引用类型的引用都是保存在ThreadLocal实例中的,但实际上不是的,这种说法只是让我们更好的理解ThreadLocal变量这个概念。向ThreadLocal存入一个值,实际上是向当前线程对象中的ThreadLocalMap存入值,ThreadLocalMap我们可以简单的理解成一个Map,而向这个Map存值的key就是ThreadLocal实例本身。

源码

Java中ThreadLocal类怎么使用

????也就是说,想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了,获取ThreadLocal的值时同样也是这个道理。这也就是为什么ThreadLocal可以实现线程之间隔离的原因了。

内部类ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,实现了一套自己的Map结构✨

ThreadLocalMap属性:

static class Entry extends WeakReference<ThreadLocal<?>> {            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;            }        }        //初始容量16        private static final int INITIAL_CAPACITY = 16;        //散列表        private Entry[] table;        //entry 有效数量         private int size = 0;        //负载因子        private int threshold;

ThreadLocalMap设置ThreadLocal 变量

private void set(ThreadLocal<?> key, Object value) {            Entry[] tab = table;            int len = tab.length;                        //与运算  & (len-1) 这就是为什么 要求数组len 要求2的n次幂             //因为len减一后最后一个bit是1 与运算计算出来的数值下标 能保证全覆盖             //否者数组有效位会减半             //如果是hashmap 计算完下标后 会增加链表 或红黑树的查找计算量             int i = key.threadLocalHashCode & (len-1);                        // 从下标位置开始向后循环搜索  不会死循环  有扩容因子 必定有空余槽点            for (Entry e = tab[i];   e != null;  e = tab[i = nextIndex(i, len)]) {                ThreadLocal<?> k = e.get();                //一种情况 是当前引用 返回值                if (k == key) {                    e.value = value;                    return;                }                //槽点被GC掉 重设状态                 if (k == null) {                    replaceStaleEntry(key, value, i);                    return;                }            }//槽点为空 设置value            tab[i] = new Entry(key, value);            //设置ThreadLocal数量            int sz = ++size;//没有可清理的槽点 并且数量大于负载因子 rehash            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

ThreadLocalMap属性介绍????:

ThreadLocalMap存储位置

储存在Thread中,有两个ThreadLocalMap变量

Java中ThreadLocal类怎么使用

threadLocals 在ThreadLocal对象方法set中去创建 也由ThreadLocal来维护

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

inheritableThreadLocals 和ThreadLocal类似 InheritableThreadLocal重写了createMap方法

void createMap(Thread t, T firstValue) {        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);    }

inheritableThreadLocals 作用是将ThreadLocalMap传递给子线程

Java中ThreadLocal类怎么使用

init方法中 条件满足后直接为子线程创建ThreadLocalMap

Java中ThreadLocal类怎么使用

注意:

Key的弱引用问题

为什么要用弱引用,官方是这样回答的

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.

为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。

生命周期长的线程可以理解为:线程池的核心线程

ThreadLocal在没有外部对象强引用时如Thread,发生GC时弱引用Key会被回收,而Value是强引用不会回收,如果创建ThreadLocal的线程一直持续运行如线程池中的线程,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

Java8中已经做了一些优化如,在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。

java中的四种引用

通常ThreadLocalMap的生命周期跟Thread(注意线程池中的Thread)一样长,如果没有手动删除对应key(线程使用结束归还给线程池了,其中的KV不再被使用但又不会GC回收,可认为是内存泄漏),一定会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,Java8已经做了上面的代码优化。

到此,关于“Java中ThreadLocal类怎么使用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯