ThreadLocal

一,前言

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样子就可以解决在并发情况下,因为多个线程访问共享变量而引发的并发问题。

也可以用作当前线程的全局变量,例如 ,通过过滤器来获取请求的用户信息,然后作为一个全局变量set到ThreadLocal里,在其他地方调用get方法来取出。

 

二,常用的方法分析

以上是ThreadLocal的类结构图,ThreadLocalMap是底层真正存储的对象,ThreadLocalMap由一个Entry类型的数组,Entry对象类似map,只不过key是当前线程,value就是我们set进去的值。

我这里主要挑出几个常见的方法来分析:

set():设置值

get():获取值

remove():移除值

setInitialValue():设置初始值

getMap():返回一个ThreadLocalsMap对象

2.1  set()

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

拿当前线程作为key去获取ThreadLocalMap对象,如果为空,则去创建一个ThreadLocalMap,并且把当前的 value作为第一个值。否则,就往ThreadLocalMap里存值。

 

2.2 get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    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;
    }

把当前线程作为key获取一个ThreadLocalMap对象,如果为空,就去设置一个初始值。在设置初始值的过程中,如果发现ThreadLocalMap也没有初始化(也就是map为null),就去创建一个value为null的ThreadLocalMap。如果ThreadLocalMap初始化了,那么就去把ThreadLocalMap的value设置成null,然后返回null。

 

2.3  remove()

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

通过当前线程拿到ThreadLocalMap对象,直接移除。

2.4  createMap()

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

创建一个ThreadLocalMap对象,key是传入的线程t,value是firstValue,在方法的调用者,Thread都是当前线程。

 

2.5  setInitialValue()

    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;
    }

初始化值。如果ThreadLocalMap还未初始化,就去创建一个ThreadLocalMap且把它的值设置为null,否则,就把它的值设置成null。

initialValue()方法的返回值是一个null。

    protected T initialValue() {
        return null;
    }

 

2.6  ThreadLocal代码小总结

观察上边的代码,可以看到,不管是set,get,还是remove,其实都是调用ThreadLocalMap来实现的。由此可以看到,ThreadLocalMap才是真正在干活的人。那么,我们就去看看ThreadLocalMap是个啥

 

2.7  ThreadLocalMap

以上是ThreadLocalMap的类结构,Entry是它存储的数据结构。

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

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

这里就是一个类似Map的结构,key是线程,value是值。而且,Entry数组作为一个散列表,在ThreadLocalMap里使用线性探测法来确定下一个元素存储的位置。注意,它这里继承了一个弱引用!是可能会造成内存泄漏的原因,下边再分析。

这里也是主要分析ThreadLocalMap的set,get,remove方法

2.7.1  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();

                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();
        }

拿到散列表table,通过线程id的哈希值计算出i,然后遍历散列表。

如果找到了对应的节点,则返回value

如果当前节点并不是要找的节点且当前节点为空,则触发清空方法,这也是为了防止内存泄漏。内存泄漏后续再讲解。

 

 

2.7.2  get()

待补充

 

2.7.3  remove()

待补充

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值