ThreadLocal实现方式剖析

ThreadLocal是用于提供支持线程间变量隔离,实现线程安全,多线程下变量同步访问有两种方式:1是通过Synchronized同步锁,多线程以排队的形式访问变量,保证线程安全,2是通过ThreadLocal实现线程间变量有多个副本,第一种方式是牺牲时间换安全,第二种是牺牲空间换安全,两种方式各有利弊。
ThrealdLocal的实现原理大致介绍下:

jdk中Thread类下有一个ThreadLocalMap类型的成员变量,而ThreaLocalMap类属于ThrealLocal类的一个静态内部类,ThreaLocalMap类的数据结构是一个Entry型的数组,而Entry类,又是ThreadLocalMap类的内部静态类,Entry类的数据结构是只有一个Object类型的成员变量,可以当作该Entry的一个具体的值value,其中需要注意的是,Entry内部类继承了弱引用接口,只有一个构造函数,其中有两个参数,第一个是一个弱类型的引用的参数,作为该Entry的标记,可以看作是该Entry的key,第二个是该Entry实际对应的value值。
前面提到Thread类下有一个ThreadLocalMap类型的成员变量,也就是说每个线程都有各自的ThreadLocalMap类型的对象,它们互相独立,这是ThreadLocal实现的关键,ThreadLocal每次取值,设置值的时候,都是在对每个线程自己的ThreadLocalMap对象进行操作,所以能互相独立,互不影响。
那对ThreadLocalMap如何进行get,set,刚才说到ThreadLocalMap内部就是一个Entry类型的数组,大家有没有想到HashMap内部类Entry,ThreadLocalMap的get set方法的实现和HashMap有很多相似之处,下面简单介绍ThreadLocalMap的存取实现逻辑。

先上ThreadLocal的set方法代码:

 public void set(T value) {
 		//获取当前线程对象
        Thread t = Thread.currentThread();
        //根据当前线程获取当前线程下的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
        //如果获取到了ThreadLocalMap,调用ThreadLocalMap的存值方法
            map.set(this, value);
        else
        //如果没有获取到ThreadLocalMap对象,创建一个ThreadLocalMap对象
            createMap(t, value);
    }

ThreadLocal的set方法实际是调用了ThreadLocalMap的set方法,下面再看ThreadLocalMap类的set方法:

 private void set(ThreadLocal<?> key, Object value) {
			//获取ThreadLocalMap的静态内部类Entry对象数组
			//该Entry数组就是ThreadLocalMap实际的数据结构
            Entry[] tab = table;
            //获取entry数组长度
            int len = tab.length;
            //获取当前ThreadLocal对象的hash值,并对数组长度取模,
            //得出一个小于数组长度的值,作为新值存放的数组下标
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //获取数组中该元素Entry的key值
                ThreadLocal<?> k = e.get();

                if (k == key) {
                //如果该Entry的key值和需要设置的key值地址相同
                //那么赋予Entry一个新的value
                    e.value = value;
                    return;
                }

                if (k == null) {
               //如果改Entry元素的key值为null,说名该Entry元素已经无法引用了
               //回收该Entry(或者说清理该Entry占用的一个槽)
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
	//执行到此处时,tab[i]一定为null,说明Entry数组的该位置没有对象
	//那么new一个Enry对象,前面说过构造Entry需要两个参数,一个是弱类型引用
	//还有一个时具体的值
            tab[i] = new Entry(key, value);
     //Entry的个数加一 注意 Entry数组的长度不等于个数,
     //个数指的是不为null的元素的数量,长度是包含null元素的数量      
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
            //清理没有使用的Entry元素,如果没有清理成功且Entry个数大于阀值
            //重新hash扩容
                rehash();
        }

这里有个Entry数组个数和阀值的比较,阀值是数组大小的三分之二,也就是说如果Entry非null元素的个数达到了数组长度的三分之二及以上,那么就需要hash扩容了。至于为什么是数组长度的三分之二,感兴趣可以去查。
nextIndex方法就是获取数组指定索引的下一个索引,如果下一个索引超过了数组的长度,就返回数组第一个索引0.

下面用一张图描述 Thread ThreadLocal TreadLocalMap Entry这几个主要类的关系:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值