首页 > 代码库 > ThreadLocal实现原理

ThreadLocal实现原理

 
一、ThreadLocal介绍
    这是一个线程的局部变量。也就是说,只有当前线程可以访问。既然是只有当前线程可以访问的数据,自然是线程安全的。
    为每一个线程分配不同的对象,需要在应用层面保证。ThreadLocal只是起到了简单的容器作用。
 
二、实现原理
   1. 我们需要关注的是ThreadLocal的set()方法和get()方法。
   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()拿到线程的ThreadLocalMap,并将值设入ThreadLocalMap中。而ThreadLocalMap可以理解为一个Map(虽然不是,但是你可以把它简单地理解成HashMap),但是它是定义在Thread内部的成员。注意下面的定义是从Thread类中摘出来的:
    ThreadLocal.ThreadLocalMap threadLocals = null;
    而设置到ThreadLocal中的数据,也正是写入了threadLocals这个Map。其中,key为Thread-Local当前对象,value就是我们需要的值。而threadLocals本身就保存了当前自己所在线程的所有“局部变量”,也就是一个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();
    }
    首先,get()方法也是先取得当前线程的ThreadLocalMap对象。然后,通过将自己作为key取得内部的实际数据。
 
    2.Thread类会进行一些清理工作,其中就包括清理ThreadLocalMap
    这些变量是维护在Thread类内部的(ThreadLocalMap定义所在类),这也意味着只要线程不退出,对象的引用将一直存在。
    下面是Thread中的方法:
    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
 
所以当我们使用线程池的时候,那就意味着线程未必会退出(比如固定大小的线程池,线程总是存在)
    如果这样,将一些大大的对象设置到ThreadLocal中(它实际保存在线程持有的thread-Locals Map内),可能会使系统出现内存泄露的可能(这里我的意思是:你设置了对象到ThreadLo-cal中,但是不清理它,在你使用几次后,这个对象也不再有用了,但是它却无法被回收)。
    此时,如果你希望及时回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。就像我们习惯性地关闭数据库连接一样。如果你确实不需要这个对象了,那么就应该告诉虚拟机,请把它回收掉,防止内存泄露。
 
    另外一种有趣的情况是JDK也可能允许你像释放普通变量一样释放ThreadLocal。比如,我们有时候为了加速垃圾回收,会特意写出类似obj=null之类的代码。如果这么做,obj所指向的对象就会更容易地被垃圾回收器发现,从而加速回收。同理,如果对于ThreadLocal的变量,我们也手动将其设置为null,比如tl=null。那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。
 
    要了解这里的回收机制,我们需要更进一步了解Thread.ThreadLocalMap的实现。之前我们说过,ThreadLocalMap是一个类似HashMap的东西。更精确地说,它更加类似WeakHashMap。ThreadLocalMap的实现使用了弱引用。弱引用是比强引用弱得多的引用。Java虚拟机在垃圾回收时,如果发现弱引用,就会立即回收。Thread-LocalMap内部由一系列Entry构成,每一个Entry都是WeakReference<ThreadLocal>:
     
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = http://www.mamicode.com/v;
            }
        }
这里的参数k就是Map的key,v就是Map的value。其中k也就是ThreadLocal实例,作为弱引用使用(super(k)就是调用了WeakReference的构造函数)。因此,虽然这里使用ThreadLocal作为Map的key,但是实际上,它并不真的持有ThreadLocal的引用。而当ThreadLocal的外部强引用被回收时,ThreadLocalMap中的key就会变成null。当系统进行ThreadLocalMap清理时(比如将新的变量加入表中,就会自动进行一次清理,虽然JDK不一定会进行一次彻底的扫描,但显然在我们这个案例中,它奏效了),就会自然将这些垃圾数据回收。
技术分享
 
三、对性能的影响
    为每一个线程分配一个独立的对象对系统性能也许是有帮助的。当然了,这也不一定,这完全取决于共享对象的内部逻辑。如果共享对象对于竞争的处理容易引起性能损失,我们还是应该考虑使用ThreadLocal为每个线程分配单独的对象。
    一个典型的案例就是在多线程下产生随机数。在多线程共享一个Random实例的情况下,总耗时达13秒之多(这里是指4个线程的耗时总和,不是程序执行的经历时间)。而在ThreadLocal模式下,仅耗时1.7秒左右。
 
 
 
 
 
 
 
 
 
 
 

ThreadLocal实现原理