ThreadLocal流程深入分析

继续来探究引用相关的知识,这次来研究一下JDK中的ThreadLocal这个类,关于这个类其实在当时https://www.cnblogs.com/webor2006/p/11630538.html研究Android中Handler机制已经详情了解过了,不过视角会不一样,这次是带着对对象引用的视角来审视它,因为它里面使用到了WeakReference。

概述:

我们知道在多线程的情况下访问一个共享资源如果不上锁的话势必会被不同的线程所修改,造成数据读取出来的值可能并非如预期, 而ThreadLocal是一种不需要上锁也能保证线程之间数据同步的问题,简单来说,ThreadLocal采取的是一种空间换时间的策略,每个线程有独立的数据副本,比如说有5个线程,则5个副本就存在于线程的上下文当中, 使得每一个线程都有一个变量独立的副本,而这个副本无论如何也不可能被其它的线程所访问到,也就不可能被其它线程所篡改,显然就达到了上锁的效果,达到线程变量的一种隔离。

简单使用:

先来简单对它进行一个使用,比较简单:

package com.jvm.reference;

public class MyTest6 {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();

        threadLocal.set("hello");
        threadLocal.set("world");

        new Thread(() -> {
            threadLocal.set("thread1");
            System.out.println("thread1:" + threadLocal.get());
        }).start();

        new Thread(() -> {
            threadLocal.set("thread2");
            System.out.println("thread2:" + threadLocal.get());
        }).start();

        System.out.println(threadLocal.get());
    }
}

编译运行:

/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/bin/java -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/lib/tools.jar:/Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/mysql/mysql-connector-java/5.1.34/46deba4adbdb4967367b013cbc67b7f7373da60a/mysql-connector-java-5.1.34.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/cglib/cglib/3.2.0/bced5c83ed985c080a24dc5a42b0ca631556f413/cglib-3.2.0.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/5.0.3/dcc2193db20e19e1feca8b1240dbbc4e190824fa/asm-5.0.3.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.apache.ant/ant/1.9.4/6d473e8653d952045f550f4ef225a9591b79094a/ant-1.9.4.jar:/Users/xiongwei/.gradle/caches/modules-2/files-2.1/org.apache.ant/ant-launcher/1.9.4/334b62cb4be0432769679e8b94e83f8fd5ed395c/ant-launcher-1.9.4.jar com.jvm.reference.MyTest6
thread1:thread1
thread2:thread2
world

Process finished with exit code 0

这块不多说了,基本上对于该类的具体用处都比较了解,接下来则来分析一下它的整个动作流程。

流程分析:

首先来读一下该类的官方说明:

接下来一段就是咱们要研究的重点了,因为跟引用相关:

下面再来挖掘一下这个类的细节:

然后传了一个泛型T,它有两个构造方法:

在上面的代码示例中咱们用到了它里面很重要的两个方法:

下面则来分析一下这俩方法的执行逻辑,其实在之前Handler的底层原理时对于这块也分析过,不过这里再来重新梳理一下,进一步加深理解:

ThreadLocal.set():

看一下它的基本逻辑,首先获取当前的线程:

跟进去:

继续跟进去:

回到主流程继续往下分析:

也就是直接开始设置值了,瞅一眼:

整个set()方法的代码逻辑为:

/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            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;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

暂且先不看细节,再回到主流程看else条件,则开始创建一个ThreadLocalMap:

很显然第一次调用set()方法肯定会走这个创建map的过程,看一下它的创建细节,很关键了:

可以看到Thread、ThreadLocal、TreadLocalMap就建议了联系了,这个需要牢记这种关系,因为之后的理解都是建议在这个关系理解基础之上的,简单来说就是最终的值是设置到了当前线程里面的ThreadLocalMap对象里面了。

ThreadLocal.get():

好,对于set()方法暂时就先看到这,接下来再来看一下get()方法:

 

 

对于第一次调用get()方法肯定会走上面的这个流程,而如果map获取到了,则会走这里面了:

看一下这块的细节:

 

首先根据当前的ThreadLocal来获取Entry对像,这个Entry对象就是我们最最要关心的啦,瞅一眼它的定义:

这块的细节先不管,先来看一下Entry这个的定义,新大陆就会出现:

那。。为啥它要来继承弱引用呢?很显然它是为了解决内存溢出的问题,但是如果不使用它为啥就会有内存溢出出现呢?关于这块的理解才是学习最有意义的,这个对于实际我们在工作当中编写代码时也是非常有意义的,毕境弱引用在解决内存溢出方面是起到了非常重要的作用,这一切的分析下次再继续~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ThreadLocalJava中的一个线程本地变量,它提供了一种简单的解决多线程并发访问共享变量的方案。每个ThreadLocal对象维护了一个独立的变量副本,每个线程都可以访问自己的变量副本,从而避免了多线程之间对同一变量的竞争。 ThreadLocal的原理可以简单概括为以下几个步骤: 1. 每个Thread对象内部都有一个ThreadLocalMap对象,ThreadLocalMap是ThreadLocal的核心数据结构,用于存储每个ThreadLocal对象对应的变量副本。 2. 在ThreadLocal对象中调用set()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后将当前ThreadLocal对象作为key,将要存储的变量副本作为value,存储到ThreadLocalMap中。 3. 在ThreadLocal对象中调用get()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后以当前ThreadLocal对象作为key获取对应的变量副本,从而实现了多线程之间的变量隔离。 4. 在ThreadLocal对象中调用remove()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后将当前ThreadLocal对象从ThreadLocalMap中删除。 需要注意的是,由于ThreadLocalMap是存储在线程中的,因此需要注意内存泄漏的问题。如果在ThreadLocal对象使用结束后没有调用remove()方法,会导致ThreadLocal对象一直存在于ThreadLocalMap中,从而引起内存泄漏。因此,在使用ThreadLocal对象时,需要注意及时调用remove()方法,以避免内存泄漏问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

webor2006

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值