ThreadLocal源码学习

本文是在自己学习ThreadLocal过程中,记录下有意思的东西,以及自己的一些想法。

ThreadLocal作用

在Thread类中声明了一个叫做threadLocals的变量,类型是ThreadLocal的静态内部类ThreadLocalMap 。

public class Thread implements Runnable {
...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

从注释可以看出该类是和线程有关的,因为每个线程线程都会独立初始化该变量。 threadLocals 类型是ThreadLocalMap ,那么肯定是用键值对读写的,其中key值为ThreadLocal对象,而value值为我们要保存的对象,一个ThreadLocal对象对应一个Value值。因此threadLocals 作用主要是保存、读取线程变量

ThreadLocal源码分析

public class ThreadLocal<T> {

	//创建时生成的哈希值,用来在map中找到散列位置。
    private final int threadLocalHashCode = nextHashCode(); 
	//一个原子整数
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    
    //每创建一个ThreadLocal对象,对应的threadLocalHashCode值增加0x61c88647。
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    
    /**
     * 赋值操作,将value存储到当前线程的threadLocals中
     * 泛型T即为我们声明和保存的对象类型。
     */
    public void set(T value) {
        //首先获得当前的线程
        Thread t = Thread.currentThread();
        //获取当前线程的保存键值对的threadLocals变量,详见分析一。
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //ThreadLocalMap.set方法分析见下文。
            map.set(this, value);
        else
        	//若该map不存在,则初始化该线程的ThreadLocalMap,详见分析二 。
            createMap(t, value);
    }

    //以该ThreadLocal对象为key值,读取当前线程中对应的value值。
    public T get() {
        //获得当前线程
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //调用ThreadLocalMap.get方法获得,ThreadLocalMap中保存键值对的对象Entry,分析见下文。
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T) e.value;
                return result;
            }
        }
        //若没有对应的value值,则返回初始值,见分析三
        return setInitialValue();
    }

    //从当前线程的threadLocals中,移除以该TheadLocal对象为key值的键值对
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            //调用ThreadLocalMap.remove方法,分析见下文。
            m.remove(this);
    }
    
	//分析一,获得当前线程的threadLocals变量
    ThreadLocalMap getMap(Thread t) {
    	//直接返回该线程的threadLocals变量
        return t.threadLocals;
    }
    //分析二,初始化当前线程的threadLocals变量
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
	/**
	 *分析三
	 *get操作没得到value值时,返回默认的value值
	 *同时将该键值对加入到map中
	 */
    private T setInitialValue() {
    	//获得初始化值
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    //默认初始化值为null,可以通过继承覆写该方法。
    protected T initialValue() {
        return null;
    }
    
	//保存键值对的map
    static class ThreadLocalMap {}
}

set、get、remove操作都是先获得当前线程的ThreadLocalMap变量 ,然后以该ThreadLocal对象(即自身)为Key值,操作ThreadLocalMap变量。
因此声明一个static ThreadLocal对象后,在不同的线程调用会获得不同的值,使用时注意所在的线程。

ThreadLocal.ThreadLocalMap源码分析

    static class ThreadLocalMap {
    
    	//该Map通过Entry数组来存储数据
        private Entry[] table;
        //数组中Entry的个数
        private int size = 0;
        //需要扩容的阈值
        private int threshold
    	
    	//Entry包括了一个Key值,一个value指,其中key值是一个弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
            	//key是一个弱引用
                super(k);
                value = v;
            }
        }
        
        //把该数组当成了一个环形
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
        
        //构造函数
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	//数组初始化大小为16 
            table = new Entry[INITIAL_CAPACITY];
            //通过ThreadLocal.threadLocalHashCode值,计算散列位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            //数组长度的2/3当做阈值
            setThreshold(INITIAL_CAPACITY);
        }

		//通过key获得value值
        private Entry getEntry(ThreadLocal<?> key) {
        	//通过threadLocalHashCode计算散列位置
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
            	//发生了冲突,从i开始向后扫描
                return getEntryAfterMiss(key, i, e);
        }
        
        //发生了冲突,从i位置向后扫描
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                	//弱引用对应的对象不存在了,清除该对象
                    expungeStaleEntry(i);
                else
                	//向后查找
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

		//插入键值对
        private void set(ThreadLocal<?> key, Object value) {
        
            Entry[] tab = table;
            int len = tab.length;
            // 计算key值的散列位置
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //已存在的key值,覆盖旧的value值
                if (k == key) {
                    e.value = value;
                    return;
                }
				//因为key值是个弱引用,引用已经不存在了,需要进行清除
                if (k == null) {
                	//清理并插入新的键值对
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//插入键值对
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //判断是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
            	//首先清除所有的失效的弱引用,若还是不够,进行扩容。容量*2,重新计算散列的位置,
                rehash();
        }
        
		//根据key值移除键值对
        private void remove(ThreadLocal<?> key) {
            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)]) {
                 //根据key值,找到数组中对应的位置
                if (e.get() == key) {
                    e.clear();
                    //删除tab[i]的对象
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
        
        //清理失效的Entry
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            //清理即将对象置为null
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            //重新计算相邻的entry的位置
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        //h为新的位置
                        tab[h] = e;
                    }
                }
            }
            //下一个空槽的位置
            return i;
        }

    
    }

key值使用弱引用

这里为什么key值为什么要使用弱引用,可以看下ThreadLocal的内存泄露问题。使用弱引用主要是为了解决内存泄漏的问题,如果ThreadLocal变量不在了,那么该引用key值也就没必要存在了,再根据key值将Entry删掉。为了避免两个生命周期不一样的对象,在引用时发生内存泄漏的问题。

数组长度为2^n

说下为什么Entry[]长度一定要是2n,HashMap、ArrayDeque 数组长度也是2n,原理是一样的。
x %2n = x &( 2n -1) 一个数x对2n求余时,可以转成 x &( 2n -1) ,位运算速度肯定是比求模运算快的。
想要把%运算转成&运算,只有当 X%2n才能实现,所以数组的长度必须是2n
想要知道为什么可以看看这篇文章计算一个数与2的n次方取模。

get、set、remove操作

在这里插入图片描述

get、set、remove操作发现null key值时,就会将该Entry置空,并重新hash连续的Entry。
但如果你的ThreadLocal已经不在了,但你没有调用get、set、remove操作,还是会出现内存泄漏的问题,所以在不使用某个ThreadLocal时,主动调用remove的方法,来避免该问题。

关于threadLocalHashCode

这个threadLocalHashCode 十分有意思。
用该threadLocalHashCode % 2n 时,可以均匀的分配在Entry数组中,当有多个ThreadLocal加入时,减少冲突发生的概率,同时配合链地址法来解决hash表的冲突问题。那么为什么这个神秘数字可以均匀的分配在2^n数组上,看看这个文章ThreadLocal 和神奇数字 0x61c88647,原因是和黄金比例有关。

InheritableThreadLocal父线程向子线程传递变量

InheritableThreadLocal的使用如下

    private static InheritableThreadLocal<String> mInheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String args[]) {
        mInheritableThreadLocal.set("主线程赋值");
        new Thread(new Runnable() {
            @Override
            public void run() {
            	//获得字符"主线程赋值"
                String s = mInheritableThreadLocal.get();
            }
        }).start();
    }

现在分析下原因,首先看下Thread的构造函数

	//声明inheritableThreadLocals 
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null
	//使用new Thread时的构造函数
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    	//parent是当前调用所在的线程
        Thread parent = currentThread();
        if (g == null) {
            g = parent.getThreadGroup();
        }
        g.addUnstarted();
        //复制父线程的变量
        this.group = g;
        this.target = target;
        this.priority = parent.getPriority();
        this.daemon = parent.isDaemon();
        setName(name);
        init2(parent);
    }
    
    private void init2(Thread parent) {
        ...
        //如果父线程的inheritableThreadLocals不为空,则复制父线程的inheritableThreadLocals
        if (parent.inheritableThreadLocals != null) {
        	//调用createInheritedMap方法,将父线程的inheritableThreadLocals复制给子线程的inheritableThreadLocals
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
                    parent.inheritableThreadLocals);
        }
    }

子线程在创建时候,如果父线程的inheritableThreadLocals 存在,则会复制该变量给子线程的inheritableThreadLocals 。

看下InheritableThreadLocal相关代码

//继承自ThreadLocal
public class createInheritedMap<T> extends ThreadLocal<T> {
    
    //对parentValue进行处理,目前无修改。若有需要可以覆写该方法进行处理
    protected T childValue(T parentValue) {
        return parentValue;
    }
	//覆写了getMap,返回的是inheritableThreadLocals而不是原来的threadLocals
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
	//覆写了createMap,为inheritableThreadLocals变量进行初始化
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal的代码很少,调用InheritableThreadLocal.set方法时候,会为初始化当前线程初的inheritableThreadLocals 。同样调用InheritableThreadLocal.get方法时,会从当前线程的inheritableThreadLocals 查找变量。

接下来看下ThreadLocal.createInheritedMap方法

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        //调用了ThreadLocalMap的构造方法,parentMap即父线程的inheritableThreadLocals变量。
        return new ThreadLocalMap(parentMap);
    }
    
    static class ThreadLocalMap {
    
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            //初始化子线程的table 
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                   
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    //复制父线程中key值有效的键值对
                    if (key != null) {
                    	//调用createInheritedMap覆写方法childValue,此时返回e.value。
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        //计算散列位置
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        //插入Entry
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
    }

该方法复制了父线程的inheritableThreadLocals中有效的键值对给子线程,在子线程也有一个副本。
因此当父线程调用inheritableThreadLocals.set()后,子线程通过inheritableThreadLocals.get()可以获父线程中声明的变量。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
未来社区的建设背景和需求分析指出,随着智能经济、大数据、人工智能、物联网、区块链、云计算等技术的发展,社区服务正朝着数字化、智能化转型。社区服务渠道由分散向统一融合转变,服务内容由通用庞杂向个性化、服务导向转变。未来社区将构建数字化生态,实现数据在线、组织在线、服务在线、产品智能和决策智能,赋能企业创新,同时注重人才培养和科研平台建设。 规划设计方面,未来社区将基于居民需求,打造以服务为中心的社区管理模式。通过统一的服务平台和应用,实现服务内容的整合和优化,提供灵活多样的服务方式,如推送式、订阅式、热点式等。社区将构建数据与应用的良性循环,提高服务效率,同时注重生态优美、绿色低碳、社会和谐,以实现幸福民生和产业发展。 建设运营上,未来社区强调科学规划、以人为本,创新引领、重点突破,统筹推进、整体提升。通过实施院落+社团自治工程,转变政府职能,深化社区自治法制化、信息化,解决社区治理中的重点问题。目标是培养有活力的社会组织,提高社区居民参与度和满意度,实现社区治理服务的制度机制创新。 未来社区的数字化解决方案包括信息发布系统、服务系统和管理系统。信息发布系统涵盖公共服务类和社会化服务类信息,提供政策宣传、家政服务、健康医疗咨询等功能。服务系统功能需求包括办事指南、公共服务、社区工作参与互动等,旨在提高社区服务能力。管理系统功能需求则涉及院落管理、社团管理、社工队伍管理等,以实现社区治理的现代化。 最后,未来社区建设注重整合政府、社会组织、企业等多方资源,以提高社区服务的效率和质量。通过建立社区管理服务综合信息平台,提供社区公共服务、社区社会组织管理服务和社区便民服务,实现管理精简、高效、透明,服务快速、便捷。同时,通过培育和发展社区协会、社团等组织,激发社会化组织活力,为居民提供综合性的咨询和服务,促进社区的和谐发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值