美文网首页
ThreadLocal源码解析

ThreadLocal源码解析

作者: HurryYu_YZH | 来源:发表于2018-11-25 16:13 被阅读0次

ThreadLocal源码阅读记录,如有不妥,欢迎指出,共同学习,谢谢!

一、简单使用

ThreadLocal可以理解为线程本地变量,使用它,可以将变量与线程之间产生关联,使得变量的隔离级别在同一个线程中.我们通过非常简单的代码,来简单使用下ThreadLocal:

private static ThreadLocal<Integer> mThreadLocal;

public static void main(String[] args) {
    mThreadLocal = new ThreadLocal<>();
    mThreadLocal.set(50);
    System.out.println("当前线程:" + Thread.currentThread().getName() +
            ",ThreadLocal取出值:" + mThreadLocal.get());

    new Thread(() ->
            System.out.println("当前线程:" + Thread.currentThread().getName() +
                    ",ThreadLocal取出值:" + mThreadLocal.get())
    ).start();
}

上面的代码非常简单,首先我们在主线程往mThreadLocal中set了一个Integer类型的变量,值为50.紧接着我们马上取出值:mThreadLocal.get(),得到了刚才存进去的值:50.然后我们在子线程中再次尝试取出值,但却发现取出来的结果为:null.因此上面代码的运行结果为:

当前线程:main,ThreadLocal取出值:50
当前线程:Thread-0,ThreadLocal取出值:null

这也证实了,使用ThreadLocal存储的变量,是属于当前线程的,在其它线程是访问不了的.

二、源码阅读

首先我们还是从最简单的使用说起:ThreadLocal<Integer> threadLocal = new ThreadLocal<>(),显然这里可以看出ThreadLocal是一个泛型类,其泛型表示希望存储数据的数据类型,ThreadLocal的构造器如下:

/**
 * Creates a thread local variable.
 * @see #withInitial(java.util.function.Supplier)
 */
public ThreadLocal() {
}

似乎没什么好说的... 接着我们再来看 threadLocal.set(50),这里又是怎么回事呢?跟进到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);
        }
    }

这里首先获取到了当前的线程,然后调用了getMap方法,得到了一个ThreadLocalMap对象引用.这个ThreadLocalMap是啥呢?它是ThreadLocal的一个静态内部类,这里先大概知道这样就行,还是先看下getMap()方法是怎么回事:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
问号猫

看到这里,原来ThreadLocalMap的引用存储在Thread类的成员变量中:

    ThreadLocal.ThreadLocalMap threadLocals = null;

好吧,回到我们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);
        }
    }

继续往下看,因为我们目前是第一次set值,所以getMap返回的是null,也就是这里的map为null,所以会走createMap(Thread t, T firstValue)方法,我们又跟进到此方法中看一看:

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

实际上就是创建了一个ThreadLocalMap对象,并把对象引用赋值给当前Thread对象中的threadLocals字段.继续看下ThreadLocalMap的构造器:

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

首先看下这个构造器的参数列表,第一个参数为ThreadLocal对象,传给它的是this(当前类[TreadLocal]对象),第二个参数为Object类型变量,传给它的是调用set方法时传进来的参数,也就是要保存的数据.明白构造器两个参数都接收的是什么东西后,再接着看代码.
第一行:table = new Entry[INITIAL_CAPACITY]; Entry是什么呢?它是ThreadLocalMap的静态内部类:

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

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

可以看到这个类继承了WeakReference,代表Entry对象弱引用了ThreadLocal,并将用户通过set方法传进来的内容赋值给成员变量value.再来看下table的定义:private Entry[] table;,因此第一行table = new Entry[INITIAL_CAPACITY];就是声明并初始化了一个Entry数组,数组的长度为INITIAL_CAPACITY也就是16(private static final int INITIAL_CAPACITY = 16;)
继续回到ThreadLocalMap的构造器往下看:

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

第二行以后是将当前ThreadLocal对象的HashCode 和 table数组长度-1 做了与运算,得到了一个int类型的值,将这个值作为数组table的下标,并将Entry对象引用作为值存储.
至此,就完成了对set数据的存储.回顾一下上面的流程,实际上就是:每一个Thread中都存储有ThreadLocalMap的引用,每次调用set方法时,都会获取当前线程的ThreadLocalMap进行存储数据,而ThreadLocalMap内部有一个Entry数组table,以当前ThreadLocal的HashCode & table.length-1为下标,Entry中存储的是当前ThreadLocal对象的弱引用和set保存的数据.如果已经通过ThreadLocal对象往当前线程中设置了数据,再次往该线程中设置数据时:

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

这个map就不再是null了,因此会走ThreadLocalMap的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();
    }

首先获取当前Thread中的ThreadLocalMap中的table,赋给tab.用同样的方法得到此ThreadLocal对象的下标,如果tab[i]得到的Entry不为null,表示曾经使用过当前ThreadLocal对象往当前线程中设置过数据,则会进入for循环,若tab[i]为null,表示当前ThreadLocal对象从未在该线程中设置过数据,则会执行for循环后面的代码,在table中新增一条记录.再来看tab[i]不为null的情况,for循环首先判断Entry的key也就是ThreadLocal的引用是否和当前的ThreadLocal引用相同,如果相同,则修改Entry中的value为新值,循环结束.如果k==key不满足,则会去判断k是否为null,若果k为null,则会调用replaceStaleEntry方法,继续寻找有无相同key的Entry,如果有,直接替换Entry的value为新值,若果没有,则会创建新的Entry.
好了,设置值的部分大概就这样了吧,这只是个人理解,如有不正确的地方,欢迎指出.接下来看看获取值得部分.
threadLocal.get(),同样,我们先看下ThreadLocal的get方法, 想都不用想,肯定是先从当前线程获取到ThreadLocalMap:

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

显然如果我们从来没有使用ThreadLocal给该线程set过数据,那么这里的map肯定是null,则会调用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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

简要看下这个方法的作用,首先调用initialValue()方法得到value,然后获取当前线程的ThreadLocalMap,如果map为null,则创建map(设置进去的值为value的值),否则直接调用set方法进行设置值,最后都会返回value的值.那最重要的就是看下initialValue()方法返回的是什么了?

    protected T initialValue() {
        return null;
    }

因此.如果我们从来没有在当前线程使用ThreadLocal设置过数据,直接调用ThreadLocal的get方法,将会返回null.继续回到ThreadLocal的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();
    }

如果我们之前使用ThreadLocal在当前线程中设置过数据,那么map就不为null,然后会调用ThreadLocalMap中的getEntry()方法获取Entry:

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

还是一样的方法获取此ThreadLocal的下标,如果在table中找得到,说明使用此ThreadLocal在当前线程中set或get过,直接返回e,然后再返回e.value就获取到值了.如果在table里找不到下标为i的Entry或是找到了,但是e.getKey()== key不成立的话,则会调用并返回getEntryAfterMiss()方法

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

这个方法中,首先就是看Entry是否是null,如果不是null,说明曾在当前线程使用过该ThreadLocal,只是e.getKey()== key不成立,在while循环中,如果k == null则说明Entry对ThreadLocal的弱引用丢了,那么就会调用expungeStaleEntry方法清除脏数据,也就是把table[i]置为null,把Entry中的value置为null避免内存泄漏.如果k != null && k!=key,就会继续往下查找Entry,并重复执行上面的操作.

到此,ThreadLocal的set和get分析完成.因个人能力有限,如有错误之处,欢迎指出.

相关文章

  • Netty 源码分析-目录

    Netty源码分析1 - Promise异步框架的设计Netty源码分析2 - ThreadLocal 源码解析N...

  • Netty源码分析3 - FastThreadLocal 框架的

    在 第二章 ThreadLocal源码解析 详细分析了jdk本身的ThreadLocal源码。本节来看一下nett...

  • ThreadLocal原理解析(2):ThreadLocalMa

    我的CSDN博客同步发布:ThreadLocal原理解析(2):ThreadLocalMap源码解析 转载请注明出...

  • ThreadLocal源码解析

    一、使用场景 ThreadLocal用于不同线程获取各自数据,同一个线程也可根据不同的threadlocal对象获...

  • ThreadLocal源码解析

    ThreadLocal 1. ThreadLocal 是什么? 源码中解释到 ThreadLocal 就用于存储数...

  • ThreadLocal源码解析

    引言 ThreadLocal,线程变量,线程可以将本次线程内经常使用的变量存储到ThreadLocal中,方便本次...

  • threadLocal源码解析

    简单记录一些看threadLocal时遇到比较有意思的点。 ThreadLocalMap ThreadLocal的...

  • ThreadLocal源码解析

    概述 ThreadLocal,即线程本地变量,它为每一个使用该变量的线程提供独立的变量副本,每一个线程都可以独立地...

  • ThreadLocal源码解析

    ThreadLocal源码阅读记录,如有不妥,欢迎指出,共同学习,谢谢! 一、简单使用 ThreadLocal可以...

  • ThreadLocal源码解析

    1.概述 多线程并发时用于存储当前线程的本地变量副本。 2. ThreadLocal解析 set:用于存储当前线程...

网友评论

      本文标题:ThreadLocal源码解析

      本文链接:https://www.haomeiwen.com/subject/hebpqqtx.html