欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ThreadLocal

程序员文章站 2022-03-22 10:42:10
一,前言ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样子就可以解决在并发情况下,因为多个线程访问共享变量而引发的并发问题。也可以用作当前线程的全局变量,例如 ,通过过滤器来获取请求的用户信息,然后作为一个全局变量set到ThreadLocal里,在其他地方调用get方法来取出。二,常用的方法分析以上是ThreadLocal的类结构图,ThreadLocalMap是底层真正存储的对象,ThreadLocalMap由一个Entry类型的数组,Entr....

一,前言

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

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

 

二,常用的方法分析

ThreadLocal

以上是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

ThreadLocal

以上是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()

待补充

 

本文地址:https://blog.csdn.net/A_droid/article/details/111500997

相关标签: 并发源码 java