源码解析ThreadLocal如何做到线程之间的数据隔离

前言:

    我们都知道ThreadLocal能做到线程数据隔离,那么底层到底是怎么做到的,通过解析源码来一探究竟!

首先我们来看看ThreadLocal的最重要的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);
    }

 1、Thread t = Thread.currentThread();

首先获取当前正在执行的线程t,

2、ThreadLocalMap map = getMap(t);

//ThreadLocal类中的方法:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }


//Thread类中方法:
ThreadLocal.ThreadLocalMap threadLocals = null;

原来当前线程Thread中有一个ThreadLocalMap对象,我们来看看这个ThreadLocalMap对象:

 static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

很明显ThreadLocalMap是一个存在与ThreadLocal的静态内部类,同时包含一个Entry成员对象(Entry也是一个静态内部类),从Entry的构造函数可以看出来,key=ThreadLocal,value是一个object对象(现在可以假想就是要设置的值),接下来我们回到set方法上;

3、if (map != null)  map.set(this, value);

接着主要看看ThreadLocalMap的set方法,其实是调用的静态内部类Entry的set方法:

  private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
            
            Entry[] tab = table;
            int len = tab.length;
            //获取当前key在Entry数组中的索引位置i
            int i = key.threadLocalHashCode & (len-1);
            //获取索引位置i,也就是在tab数组的起始位置tab[i],往后循环查找
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //获取当前Entry对象的key值(是ThreadLocal对象)
                ThreadLocal k = e.get();
                //比较key对象是否相等,就是比较是否存在相同的ThreadLocal
                //如果已经存在,替换value值,并返回
                if (k == key) {
                    e.value = value;
                    return;
                }
                                
                 //如果k为null,k将被回收,则这个e已经是没意义的的元素。调用replaceStaleEntry 
                 //方法删除table中所有无意义的元素.并插入新元素,返回
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //如果索引i处没有元素,则插入一个新的Entry对象
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //判断是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

这个方法大致的意思是将ThreadLocal作为Entry对象的key,要设置的值作为Entry对象的value;

4、else

        createMap(t, value);

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



  ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

如果ThreadLocalMap对象不存在,那么就new一个ThreadLocalMap,将ThreadLocal作为Entry对象的key,要设置的值作为Entry对象的value,然后将ThreadLocalMap对象设为当前对象的threadLocals成员变量;

接下来看看get方法:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //如果Map对象不等于null
        if (map != null) {
            //通过key=当前的ThreadLocal获取Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            //如果Entry对象不为null,直接获取存在的value对象
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //否则调用setInitialValue方法
        return setInitialValue();
    }


private T setInitialValue() {
        //这是一个protected方法,意思就是需要被子类覆盖重写的方法
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

 

汇总:ThreadLocal能做到线程之间数据隔离的奥秘在于每个线程对象都有一个ThreadLocal.ThreadLocalMap对象,我们可以从宏观上简单的理解为这就是一个Map(其实ThreadLocalMap里面有一个Entry数组,每一个Entry对象存储一个TheadLocal和一个value),Map的key都是ThreadLocal对象,value就是每个线程设置的值,不同的线程是不一样的,这样就做到了线程之间的数据隔离的目的;

java简单的示例:

 

package eureka.server;

public class Test {

    ThreadLocal longLocal = new ThreadLocal();
    ThreadLocal stringLocal = new ThreadLocal();

    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();

        test.set();
        System.out.println("父线程 main :");
        System.out.println(test.getLong());
        System.out.println(test.getString());

        Thread thread1 = new Thread() {
            public void run() {
                test.set();
                System.out.println("\n子线程 Thread-0 :");
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        Thread.sleep(1000);
        System.out.println("父线程 main :");
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

运行结果:

源码解析ThreadLocal如何做到线程之间的数据隔离_第1张图片

 

Thread可能会产生OOM(内存泄露),ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

Thread->ThreadLocal.ThreadLocalMap.Entry(key =ThreadLocal, value = object),key是一个弱引用,会被垃圾回收,但是value由于Thread的存在会一直存在;

 

你可能感兴趣的:(计算机基础)