• ThreadLocal


    1. ThreadLocal介绍

    特点:

    1. 线程并发:在多线程并发的场景下。
    2. 传递数据:我们可以通过ThreadLocal在同一线程内,不同组件间传递公共变量。
    3. 线程隔离:每个线程的变量都是独立的,不会互相影响。

    总结: ThreadLocal 类用来提供线程内部的变量,不同的线程之间不会相互干扰,这种变量在线程的声明周期内部起作用,减少了同一个线程内函数或组件间公共变量传递的复杂度。

    2. ThreadLocal基本方法

    在这里插入图片描述

    3. ThreadLocal和synchronized的区别

    虽然ThreadLocal模式和synchronized关键字都是用来处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同。

    synchronizedThreadLocal
    原理同步机制采用以时间换空间的方式,值提供一份变量,让不同线程排队访问。ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰。
    侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离

    4. ThreadLocal的内部结构

    JDK 1.8中ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,每个Map的key是
    ThreadLocal实例本身,value才是真正要存储的值Object.

    具体的过程:

    1. 每个Thread线程内部都有一个Map(ThreadLocalMap)
    2. Map里面存储ThreadLocal对象(key)和 线程的变量副本(value)
    3. Thread内部的Map是由ThreadLocal维护的,有ThreadLocal负责向Map 设置和获取线程的变量值。
    4. 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

    在这里插入图片描述

    5. ThreadLocal的核心方法源码

    在这里插入图片描述

    5.1 set()方法

        /**
         * 设置当前线程对应的ThreadLocal的值
         */
        public void set(T value) {
        	//获取当前线程对象
            Thread t = Thread.currentThread();
            //获取当前线程中维护的ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
            if (map != null)
            	//map不为空,调用map.set()方法设置元素Entry
                map.set(this, value); //(threadlocal实例, 变量值)
            else
            	//map为空,则调用createMap()进行ThreadLocalMap的创建、初始化
            	//并进行设置Entry元素。
                createMap(t, value);
        }
    
    	/*
    	 * 获取当前线程Thread对应维护的ThreadLocalMap
    	 */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        /**
         * 创建当前线程Thread对应维护的ThreadLocalMap,并设置Entry(k,v)
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    5.2 get()方法

        /**
         * 返回当前线程维护的ThreadLocalMap中的值。
         * 如果ThreadLocalMap不存在,则调用setInitialValue()方法,初始化。并塞入一个Entry对象,key是ThreadLocal实例,value是 null。
         */
        public T get() {
            //获取当前线程Thread对象
            Thread t = Thread.currentThread();
            //获取当前线程维护的ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
            if (map != null) {
            	//map不为空,根据键(ThreadLocal实例)获取Entry元素。
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                	//entry
                    @SuppressWarnings("unchecked")
                    //获取值
                    T result = (T)e.value;
                    return result;
                }
            }
            //map为空或entry为空,则初始化ThreadLocalMap,并塞入一个entry对象。
            return setInitialValue();
        }
    
    	/**
         * 初始化,ThreadLocalMap
         */
        private T setInitialValue() {
        	//返回一个null作为值。
            T value = initialValue();
            //获取当前线程对象
            Thread t = Thread.currentThread();
            //获取当前线程维护的ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null)
            	//map存在,设置一个entry
                map.set(this, value);
            else
            	//map不存在,调用createMap()进行ThreadLocalMap的创建、初始化
            	//并进行设置Entry元素。
                createMap(t, value); //(threadlocal实例,null)
            return value;	
        }
        
       	/*
       	 * ThreadLocal对应的的初始值--null,
       	 * 如果不想初始值是null,可以通过子类继承ThreadLocal,重写此方法修改。
     	 */
        protected T initialValue() {
            return null;
        }
    

    5.3 remove方法

    	 /**
          * 删除当前线程维护的ThreadLocalMap中,ThreadLocal对应的Entry对象。
          */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
             	 //以当前ThreadLocal为key,删除对应的Entry对象。
                 m.remove(this);
         }
    

    6. ThreadLocalMap源码分析

    ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现的。

    在这里插入图片描述

    6.1 成员变量

    //Entry[]数组的初始大小16,必须为2^n
    private static final int INITIAL_CAPACITY = 16;
    
    //存放数据的Entry数组。
    private Entry[] table;
    
    //table数组中存储了entry的个数
    private int size = 0;
    
    //数组扩容的阈值。
    private int threshold; // Default to 0
    

    6.2 Entry结构

    /*
    * Entry继承了WeakReference,以ThreadLocal为key.
    * 如果key为null,意味着这个key不再被应用,因此这时候entry也可以从table中清除。
    */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    Entry继承WeakReference,就是key(ThreadLocal)是弱引用,其目的是将ThreadLocal对象的声明周期和线程的声明周期解绑。

    6.3 弱引用和内存泄漏

    Memory leak 内存泄漏: 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至导致系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。

    **Memory overflow 内存溢出:**没有足够的内存提供给申请者使用。

    强引用: 强引用就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就表示这个对象还活着,垃圾回收器就不会回收这种对象。

    弱引用: 垃圾回收器一旦发现只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。

    如果是局部变量的话,只剩下弱引用,会被gc自动回收,只剩下value,而这个value的key此时会变成null,如果再有set get 的调用,会将key为null的value自动清除

    7. ThreadLocalMap 中 Hash冲突的解决

    7.1 ThreadLocalMap的构造器

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    	//初始化Entry数组,数组长度初始为16
        table = new Entry[INITIAL_CAPACITY];
        //计算索引位置
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //设置值
        table[i] = new Entry(firstKey, firstValue);
        //当前数组中元素个数为1
        size = 1;
        //设置阈值
        setThreshold(INITIAL_CAPACITY);//是初始值的2/3
    }
    
        //计算索引位置
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        
    	//初始数组大小
        private static final int INITIAL_CAPACITY = 16;
        
        //获取一个Hash值
    	private final int threadLocalHashCode = nextHashCode();
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
        //特使的hash值,能让hash码能均匀的分布在2^n的数组里,也就是Entry[].避免hash冲突
        private static final int HASH_INCREMENT = 0x61c88647;
        //AtomicInteger是一个原子操作的Integer类,线程安全的方式加减,适合高并发情况下使用。
        private static AtomicInteger nextHashCode = new AtomicInteger();
    

    7.2 set()方法

            private void set(ThreadLocal<?> key, Object value) {
                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();
    				//如果TheadLocal实例key已经存在,则覆盖值
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    				//不key为null,但是value不为null,说明之前的ThreadLocal对象已经被回收了,当前数组中的Entry是一个陈旧的元素。
                    if (k == null) {
                    	//用新元素代替旧元素。方式内存泄漏。
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    			//ThreadLocal对应的key不存在且没有找到陈旧的元素。则在空位置创建一个新的Entry
                tab[i] = new Entry(key, value);
                int sz = ++size;
                //cleanSomeSlots用于清除那些e.get()==null的元素
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    	
    	 //获取环形数组的下一个索引。
         private static int nextIndex(int i, int len) {
                return ((i + 1 < len) ? i + 1 : 0);
         }
    

    ThreadLocalMap使用线性探测法来解决哈希冲突,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。
    例:假设当前table长度为16,如果计算出的key的hash值为14,如果table[14]上已经有值且当前key和之前的key不一致,就发生了hash冲突,这时候将14加1得到15,取table[15]进行判断,还冲突则取table[0]进行判断,以此类推,直到可以插入。

  • 相关阅读:
    Python优雅遍历字典删除元素的方法
    【MC教程】iPad启动Java版mc(无需越狱)(保姆级?) Jitterbug启动iOS我的世界Java版启动器 PojavLauncher
    天气这么好,都外出了。顺便了解一下漏桶算法
    《天天数学》连载51:二月二十日
    设计模式-享元模式-笔记
    openvino系列教程之人脸检测 mobilenetv2
    vue面试题
    MACU-Net-用于精细分辨率遥感图像语义分割网络
    一些优雅的算法(c++)
    TomCat运行记录乱码
  • 原文地址:https://blog.csdn.net/ZHHX666/article/details/127027302