本文作者:包子也沉默

Java ThreadLocal 的使用与源码解析

包子也沉默 3年前 (2019-10-23) ( 10-23 ) 691 0条评论
摘要: l.set(0);for(inti=1;i<=5;i++){//获取数据intsum=threadLocal.get();out.printf("%s'ssum=%s ",getName(),threadLocal.get());sum+=i;//写回数据threadLocal.set(sum);}out.printf("END%s'ssum=%

GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/

ThreadLocal 主要解决的是每个线程绑定自己的值,可以将 ThreadLocal 看成全局存放数据的盒子,盒子中存储每个线程的私有数据。

readLocal提到了弱引用,这里顺便简单的说下Java中的四种引用。强引用:指new出来的对象,一般没有特别申明的对象都是强引用。这种对象只有在GCroots找不到它的时候才会被回收。软引用(So

验证线程变量的隔离性

import static java.lang.System.out;

public class Run {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    static class Work extends Thread {

        @Override
        public void run() {
            threadLocal.set(0);
            for (int i = 1; i <= 5; i++) {
                // 获取数据
                int sum = threadLocal.get();
                out.printf("%s"s sum = %s
", getName(), threadLocal.get());
                sum += i;
                // 写回数据
                threadLocal.set(sum);
            }
            out.printf("END %s"s sum = %d

", getName(), threadLocal.get());
        }
    }

    public static void main(String[] args) {
        Work work1 = new Work(),
                work2 = new Work();

        work1.start();
        work2.start();
    }
}

运行结果:

的数据的。ThreadLocalMap与HashMap不同,其中Entry是一个弱引用,这意味着每次垃圾回收运行时都会将储存的数据回收掉。而且它只使用了数组来存储键值对。ThreadLocalMap中

Thread-0"s sum = null
Thread-1"s sum = null
Thread-1"s sum = 1
Thread-1"s sum = 3
Thread-1"s sum = 6
Thread-1"s sum = 10
END Thread-1"s sum = 15

Thread-0"s sum = 1
Thread-0"s sum = 3
Thread-0"s sum = 6
Thread-0"s sum = 10
END Thread-0"s sum = 15


Process finished with exit code 0

从结果来看,两个线程的计算结果一致,ThreadLocal 中隔离了两个线程的数据。

!=null)map.set(this,value);else//为当前线程创建一个ThreadLocalMap对象createMap(t,value);}set()方法也是先获取当前线程自己的Thr

ThreadLocal 源码解析

ThreadLocalMap 内部类

ThreadLocal 中有一个 ThreadLocalMap 内部类,所以 ThreadLocal 实际上是使用一个哈希表来存储每个线程的数据的。

privateTsetInitialValue(){Tvalue=initialValue();Threadt=Thread.currentThread();ThreadLocalMapmap=get

ThreadLocalMapHashMap 不同,其中 Entry 是一个弱引用,这意味着每次垃圾回收运行时都会将储存的数据回收掉。而且它只使用了数组来存储键值对。

,则是采用弱引用的方式来实现,在上面的图中,我用虚线来表示弱引用,弱引用的意思是在JVM进行垃圾回收的时候这个引用会被回收(无论内存足够与否);试想一下,如果使用强引用并且栈中的引用消失了,那么在线程

ThreadLocalMap 中的 Entry

essfinishedwithexitcode0从结果来看,两个线程的计算结果一致,ThreadLocal中隔离了两个线程的数据。ThreadLocal源码解析ThreadLocalMap内部类在Th

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

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

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

get() 方法首先得到当前线程,然后获取当前线程的 ThreadLocalMap 对象,然后从中取出数据。

pthreadLocals=null;其实每次get()时都是先获取了线程自己的ThreadLocalMap对象,然后对这个对象进行操作。set()方法publicvoidset(Tvalue){Th

这里的 map.getEntry(this) 看起来很奇怪,在前面有这样一行代码:

ey对应的entry,在这个getEntry方法中里面做了内存泄漏的处理,大概处理逻辑就是如果threadLocal对应的Entry为null的话,让这个entry的value为null并且map中t

ThreadLocalMap map = getMap(t);

这个方法获取当前线程的 ThreadLocalMap 对象,所以,虽然 map.getEntry() 中的 key 总是 ThreadLocal 对象本身,但是每个线程都持有有自己的 ThreadLocalMap 对象。

图片去看源码,这里不再赘述。  关于内存泄漏的问题    1、在threadLocal的get、set、remove方法中,其对本身可能发生的内存泄漏都做了处理,逻辑上面也提到如果对应entry为nu

getMap() 方法

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

看到这个方法,get() 方法中 map.getEntry(this) 的迷雾就解开了。这里可以看到返回的是线程中的 threadLocals 属性。那么这里瞟一眼 Thread 的源码:

ead-1"ssum=6Thread-1"ssum=10ENDThread-1"ssum=15Thread-0"ssum=1Thread-0"ssum=3Thr

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal

其实每次 get() 时都是先获取了线程自己的 ThreadLocalMap 对象,然后对这个对象进行操作。

pismaintained*bytheThreadLocalclass.*/ThreadLocal.ThreadLocalMapthreadLocals=null;其实每次get()时都是先获取了线程

set() 方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        // 为当前线程创建一个 ThreadLocalMap 对象
        createMap(t, value);
}

set() 方法也是先获取当前线程自己的 ThreadLocalMap 对象,然后再设置数据。如果获取的哈希表为 null,则创建一个。

cal.get()方法的时候,threadLocal拿到当前线程中ThreadLocalMap中以threadLocal自身为key对应的entry,在这个getEntry方法中里面做了内存泄漏的处理

createMap() 方法

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

createMap() 方法创建一个 ThreadLocalMap 对象,该对象由线程持有。

个小程序和上面的图就可以对threadLocal有一个大概的理解了。其他的方法如set、remove等方法都大同小异,可以结合图片去看源码,这里不再赘述。  关于内存泄漏的问题    1、在threa

总结

  • ThreadLocal 可以隔离线程的变量,每个线程只能从这个对象中获取到属于自己的数据。
  • ThreadLocal 使用哈希表来存储线程的数据,而且这个哈希表是由线程自己持有的,每次获取和设值都会先获取当前线程持有的ThreadLocalMap 对象。
  • ThreadLocalMap 中的 key 总是 ThreadLocal 对象本身。
  • ThreadLocalMap 中的 Entry 是弱引用,每次 GC 运行都会被回收。
文章版权声明:除非注明,否则均为本站原创文章,转载或复制请以超链接形式并注明出处。
分享到:
赞 (0

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

发表评论

快捷回复:

评论列表 (有 0条评论, 691人围观) 参与讨论