• 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这个的定义,新大陆就会出现:

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

  • 相关阅读:
    WinForm控件常用设置(转)
    EF Core性能优化(一)
    如何更改已经释放的(released)传输请求(TR)的描述
    在新窗口调用Tcode[ABAP4_CALL_TRANSACTION]
    [代码]如何取得表/结构的列名字(cl_abap_structdescr)
    [代码]创建.ZIP压缩文件[CL_ABAP_ZIP]
    如何在表维护视图(maintenance view)上添加自定义按钮(SM30)
    [代码]基于动态内表的ALV
    物料单位转换函数[MD_CONVERT_MATERIAL_UNIT]
    拆分全路径名得到路径+文件名[STPU1_EXTRACT_FILENAME]
  • 原文地址:https://www.cnblogs.com/webor2006/p/13168512.html
Copyright © 2020-2023  润新知