美文网首页
ThreadLocal源码解析与运用(上)

ThreadLocal源码解析与运用(上)

作者: GrooveWind | 来源:发表于2017-02-16 18:02 被阅读0次

    ThreadLocal需要解决的问题是什么

    • ThreadLocal被用来“解决多线程并发”问题
    • ThreadLocal与同步机制的区别
      1. 同步机制:对于多线程访问的共享资源,通过“锁”对变量进行限制,此时变量只有一份;
      2. ThreadLocal:在多线程环境下,每个线程拷贝一份变量副本,各个线程使用自己的那一份(即所谓的Local),彼此之间互不影响;

    ThreadLocal如何解决多线程并发问题

    以下代码基于JDK1.8版本

    1. 既然每个线程都会拷贝一份变量,那么我们先看一下这个变量会存在哪里。首先来看Thread类:
    public class Thread implements Runnable {
        //......
    
        /* ThreadLocal values pertaining to this thread. This map is maintained 
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
        //......
    }
    

    显然,变量的存储肯定与ThreadLocalMap有关

    2.ThreadLocal类的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<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    首先,ThreadLocalMap是一个静态内部类;其次,通过WeakReference<ThreadLocal<?>>的修饰,ThreadLocal变为一个弱引用指向类;再次,还有一个静态内部类Entry存储键值,其中Key为弱引用的ThreadLocal本身(通过super(k)实例化)。
    再看一下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);
    }
    
    先初始化一个长度为16的Entry数组,然后通过哈希算法计算出键值的映射关系,再存到数组中就OK了。**顺带一提,这里是一个无锁并发的哈希实现,具体可以参考[世界上最简单的无锁哈希表](http://blog.jobbole.com/39186/)**
    
    对于其他的方法,暂时省略......
    

    ThreadLocal内存泄漏问题

    为什么ThreadLocal会产生内存泄漏
    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
    其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
    但是这些被动的预防措施并不能保证不会内存泄漏:
    使用线程池的时候,这个线程执行任务结束,ThreadLocal对象被回收了,线程放回线程池中不销毁,这个线程一直不被使用,导致内存泄漏。
    分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么这个期间就会发生内存泄漏。
    如何避免ThreadLocal内存泄漏
    每次使用完ThreadLocal,都要记得手动调用remove()方法,清除数据。

    下一篇介绍ThreadLocal的实战应用与扩展

    相关文章

      网友评论

          本文标题:ThreadLocal源码解析与运用(上)

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