ThreadLocal为什么这么设计,还有那些可以优化点?

问题一. 同一个线程方法之间的传参方式有那些?

1. 通过方法参数进行传参,与业务逻辑无关的通过参数,不方便。

2. 通过公共变量进行传参 如: static object param = null,这样写有很多问题,如并发问题,gc问题,几乎不这样写。

ThreadLocal按照第二种解决方式方式进行了优化。

优化:把object 放在了Thread类里面,这样object的生命周期就可以依赖Thread的生命周期,因为是线程的内部属性,天生线程安全。

问题二:线程中有多个参数的化怎么办,是不是要创建多个object属性呢?

很自然联想到用Map, 那key又如何解决呢? 就这样引入了ThreadLocal类, ThreadLocal一个最主要作用就是组map中的key。

ps: ThreadLocal取名不直观,不能见名知意,个人觉得改为ThreadParamKey之类的更加合适。

问题三:Thread与ThreadLocal两个类是如何配合的?

talk is cheap, picture is visual;

如上图,Thread持有ThreadLocalMap, ThreadLocal作为ThreadLocalMap的key(其实是数组中单个对象的一个属性, 和hashMap实现逻辑类似)。

下面上demo:

public class ThreadLocalTest {

    static ThreadLocal<String> localString = new ThreadLocal<String>();
    static ThreadLocal<Integer> localInteger = new ThreadLocal<Integer>();

    public static void main(String[] args) {
        System.out.println("Thread name :" +Thread.currentThread().getName());
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("start");
                //设置线程1中本地变量的值
                localString.set("a");
                localInteger.set(1);
            }
        },"mythread");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                //设置线程2中本地变量的值
                localString.set("b");
                localInteger.set(2);
            }
        });

        t1.start();
        t2.start();
    }
}

看下在内存中的数据结构:

一图顶万言。

参照上图,看源码应该很容易理解get与set的操作流程了。

接下来看几个有意思的实现细节。

1.Entry类

static class Entry extends WeakReference<ThreadLocal<?>> {
       /** The value associated with this ThreadLocal. */
        Object value;

      Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
     }
 }

Entery的key实现了WeakReference,为什么要实现呢?

WeakReference: 一个具有弱引用的对象,与软引用对比来说,前者的生命周期更短。当垃圾回收器扫描到弱引用的对象的时候,不管内存空间是否足够,都会直接被垃圾回收器回收。不过也不用特别担心,垃圾回收器是一个优先级比较低的现场,因此不一定很快可以发现弱引用的对象。

上面的例子会出现Entry中key为null的现象吗?

弱引用是对ThreadLocal对象来说的,当只有弱引用只想ThreadLocal时,gc的时候就会把该出现清除掉。上面的例子localString和localInteger两个对象是静态的,属于类级别,不会被gc掉。

如果localString已经被gc掉了,说明localString对象已经不引用该对象了,localString.get()不会调用了,调用的话也会保npe了,所以Entery的key实现了WeakReference。

弱引用是为了防止ThreadLocal对象内存溢出。

2.使用ThreadLoca有内存溢出的风险,到底指的是什么溢出,什么情况下才溢出。

从图中可以看出,如果线程是朝三暮四的化,是不可能有能存溢出的。

内存溢出实际上是ThreadLocalMap中的table一直不销毁,而且一直在增加导致的。

使用remove()方法可以化解改内存溢出风险。

3.remove()的到底是什么东西呢?

public void remove() {
     //拿到ThreadLocalMap
     ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
        m.remove(this);
}

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)]) {
            if (e.get() == key) {
                //设置key为null
                e.clear();
                //正在去清空table
                expungeStaleEntry(i);
                return;
            }
        }
    }

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            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;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

流程如下:

1.获得threadLocalMap

2.找到Entry在table数组中的位置

3.把Entry中的key设置为null

4.把Entry中的val设置为null

5.table中对应的Entry设置为null

6.rehash table

上面5步很好理解, 为什么最后要rehash呢。

接下来我看看下ThreadLocalMap是如何存储呢?

Map中出现hash冲突的时候如何解决的?

1.链表(包括树结构),HashMap实现

2.线性探测

 

key4计算key4.hash&(table.length-1),位置已经被可用暂用了, 所以往后需要空余的位置直接放进去。

rehash做的就是,如果key3被remove了,就应该把key4移到key3的位置。

为什么ThreadLocal有private static final int HASH_INCREMENT = 0x61c88647; 和private static AtomicInteger nextHashCode = new AtomicInteger();

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

HASH_INCREMENT增加hash的离散度,单hash冲突的时候时会,减少线性探测次数,提高性能

 

get和set的流程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值