对ThreadLocal的一点理解

一、ThreadLocal简介:

ThreadLocal类提供了四个方法:

get():返回此线程局部变量的当前线程副本中的值。
initialValue():返回此线程局部变量的当前线程的“初始值”,默认返回null,供子类重写。
remove():移除此线程局部变量当前线程的值。
set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。

ThreadLocal和线程同步机制不同,线程同步机制主要采用synchronized关键字和J.U.C中的Lock对象来实现,也就是加锁,加锁的目的是为了能让多个线程安全的共享一个变量。

ThreadLocal为每个线程创建了自己独有的变量副本,也就是说,线程可以随意的操作自己拥有的变量副本,不会对其他线程的变量副本产生影响,实现了线程隔离。

二、ThreadLocal实现原理:

ThreadLocal实现原理

1、怎么实现线程隔离

ThreadLocal类中的静态内部类ThreadLocalMap,这个MapThreadLocal实现线程隔离的精髓。

Thread类中有这样子一个成员变量:

    /* 与此线程相关的ThreadLocalMap */
    ThreadLocal.ThreadLocalMap threadLocals = null;

因此,一个线程对应有自己单独的一个ThreadLocalMap

所以ThreadLocal才可以实现线程隔离(ThreadThreadLocalThreadLocalMap三者的对应关系如图所示)

2、对象存储和获取原理

ThreadLocalset方法:

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

通过ThreadLocalset方法看出,ThreadLocalMap<k,v>结构中,key存储的是ThreadLocal本身,而value则是实际存储的值,也就是说,在ThreadLocalMap里存储了当前ThreadLocal所复制的变量副本。

ThreadLocal本身不存储值,在使用中,ThreadLocal是作为一个key,从ThreadLocalMap获取值,从ThreadLocalget方法中也可以看出来:

    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内存泄漏

1、ThreadLocal内存泄漏问题概述

ThreadLocalMap的静态内部类Entry

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

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

ThreadLocalMap使用静态内部类Entry实现<k,v>存储,而Entry继承WeakReference类,所以ThreadLocalMap中的key其实是ThreadLocal的一个弱引用。

正因为ThreadLocalMapThreadLocal的弱引用作为key,在这个ThreadLocal没有外部强引用的时候,会被GC。这时候,ThreadLocalMap会出现一个keynullEntry,理所当然的,这个Entryvalue将永远没办法被访问到。

在这种情况下,如果当前工作线程一直没有结束,那这个keynullvalue因为被Entry强引用,而Entry被当前线程的ThreadLocalMap强引用,导致这个value永远无法被GC,造成内存泄漏。

虽然在ThreadLocalMap的设计中,已经考虑到这种情况的发生:

  • ThreadLocalMapcleanSomeSlots()expungeStaleEntry()方法都能清除keynullvalue
  • ThreadLocalset()get()remove()方法中,都会调用cleanSomeSlots()expungeStaleEntry()来清除ThreadLocalMap中所有keynullvalue

但是这种被动清除的方式并不是在所有情况下都能保证不出现内存泄漏,反例:

  • 如果ThreadLocalset()get()remove()方法没有被调用,就会导致value的内存泄漏。
  • static修饰的ThreadLocal,导致ThreadLocal的生命周期和持有它的类一样长,意味着这个ThreadLocal不会被GC。这种情况下,如果不手动删除,Entrykey永远不为null,弱引用就失去了意义,理所当然的无法通过调用cleanSomeSlots()expungeStaleEntry()方法清除value,如果当前线程结束了,就导致了Entry的内存泄漏。
2、ThreadLocalMap的key为什么使用弱引用

看到这里可能会有这种感觉:内存泄漏都是因为key的弱引用造成的啊?那为什么不用强引用呢?

表面上看起来好像是这样子,那试想一下,如果key使用强引用,可以有这样子的对比:

  • key使用强引用:在当前ThreadLocal没有外部强引用时,ThreadLocalMapEntry还保持着ThreadLocal的强引用,ThreadLocal不会被GC。如果没有手动删除,并且当前线程结束了,就导致了Entry的内存泄漏。(有点类似用static修饰ThreadLocal的情况)
  • key使用弱引用:在当前ThreadLocal没有外部强引用时,ThreadLocalMap只保持着ThreadLocal的弱引用,无论有没有手动删除,ThreadLocal都会被GC,只要下一次cleanSomeSlots()expungeStaleEntry()被调用,value就会被清除,否则也会引起内存泄漏。

通过这样子的对比,我们可以得出:由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除,都有可能会导致内存泄漏,但是弱引用多了一层保障:使用弱引用时,ThreadLocal无论如何都不会内存泄漏,只要下一次cleanSomeSlots()expungeStaleEntry()被调用,value就会被清除。

通过对比我们也可以看出:ThreadLocal引起的内存泄漏的根源,是因为ThreadLocalMap的生命周期跟Thread一样长,并且在线程结束之前,没有删除key,并不是因为弱引用。

tips:

  • ThreadLocalMap解决哈希冲突的方法:“开放定址法”。
  • 内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,例如,有一块存储int类型数据的存储空间,但是程序员却用它存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
  • 内存泄漏 memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

参考文章:
http://cmsblogs.com/?p=2442
http://www.cnblogs.com/onlywujun/p/3524675.html

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值