ThreadLocal详解

ThreadLocal简介

ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法。
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的值。

先上代码

public class ThreadLocalTest2 {

    //(1)创建ThreadLocal变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //在main线程中添加main线程的本地变量
        threadLocal.set("mainVal");
        //新创建一个子线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程中的本地变量值:"+threadLocal.get());
            }
        });
        thread.start();
        //输出main线程中的本地变量值
        System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
    }
}
结果在这里插入图片描述

可以看出来不同线程之间的值是独立开来的。那么接下来就看看源码看看他是如何实现的吧。

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

不难看出他是通过getMap从当前线程中的获取了一个ThreadLocalMap (k,v),

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

然后把threadLocal对象自身的当成键来获取值。顺路就是利用泛式来强转一下类型。那么ThreadLocalMap到底是何方神圣???

ThreadLocalMap ThreadLocal的内部类

static class ThreadLocalMap {

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

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

只截取了相关的部分,可以看出这是一个类似与hashmap的键值对结构。但是值得注意的是WeakReference,可以看出它的key是弱引用,这会导致什么问题呢???
唏嘘,本来整理了一波,写到这里也忘了=。= 引用类型的问题。

强引用(Strong Reference)

Strong Rerence这个类并不存在,默认的对象都是强引用类型,因为有后来的新引用所衬托,所以才起了个名字叫"强引用"。

强引用使用示例如下所示:

String web = "gggs";

-如果JVM垃圾回收器 GC 可达性分析结果为可达,表示引用类型仍然被引用着,这类对象始终不会被垃圾回收器回收,即使JVM发生OOM也不会回收。而如果 GC 的可达性分析结果为不可达,那么在GC时会被回收。

软引用(Soft Reference)

软引用是一种比强引用生命周期稍弱的一种引用类型。在JVM内存充足的情况下,软引用并不会被垃圾回收器回收,只有在JVM内存不足的情况下,才会被垃圾回收器回收。所以软引用一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉。

软引用使用示例如下所示:

SoftReference<String> softReference = new SoftReference<String>(new String("gggs"));
String web = softReference.get();
弱引用(Weak Reference)

弱引用是一种比软引用生命周期更短的引用。它的生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。

WeakReference<String> weakReference = new WeakReference<String>(new String("gggs"));

System.gc();

if(weakReference.get() == null)
{
    System.out.println("weakReference已经被GC回收");
}

输出结果:

weakReference已经被GC回收

虚引用(PhantomReference)

虚引用与前面的几种都不一样,这种引用类型不会影响对象的生命周期,所持有的引用就跟没持有一样,随时都能被GC回收。

需要注意的是,在使用虚引用时,必须和引用队列关联使用。在对象的垃圾回收过程中,如果GC发现一个对象还存在虚引用,则会把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。

如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象内存被回收之前采取必要的行动防止被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

PhantomReference<String> phantomReference = new PhantomReference<String>(new String("gggs"), new ReferenceQueue<String>());

System.out.println(phantomReference.get());
对此造成的内存泄漏问题

在这里插入图片描述
这是Thread类中的成员变量。代表着每一个线程都含有一个ThreadLocalMap,而ThreadLocal只不过是一个工具类库,提供操作ThreadLocalMap的方法。首先ThreadLocal这个对象是所有的线程共享的,之所以拿到不同的值是因为不同线程中有不同的ThreadLocalMap,从ThreadlocalMap中取值是通过调用该threadlocal工具类方法的对象为key,去获取相应的value

ThreadLocalMap中的 Entry类的构造函数为 Entry(弱引用的ThreadLocal对象, Object value对象)。因为Entry的key是一个弱引用的ThreadLocal对象,所以在 垃圾回收 之前,将会清除此Entry对象的key。那么, ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value。这些 value 被Entry对象引用,所以value所占内存不会被释放。若在指定的线程任务里面,调用ThreadLocal对象的get()、set()、remove()方法,可以避免出现内存泄露。
 当key被GC回收了以后,Entry对象的key就变成null了。这个时候没法访问到 Object Value了。并且最致命的是,Entry持有Object value。所以,value的内存将不会被释放。这就造成了内存泄漏,key弱引用并不是导致内存泄漏的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,因为threadlocalmap是线程的成员变量,并且没有手动删除对应key。
因为上述的原因,在ThreadLocal这个类的get()、set()、remove()方法,均有实现回收 key 为 null 的 Entry 的 value所占的内存。所以,为了防止内存泄露(没法访问到的内存),在不会再用ThreadLocal的线程任务末尾,调用一次 上述三个方法的其中一个即可。

为什么要将ThreadLocal设计为弱引用?

因为弱引用的对象的生命周期直到下一次垃圾回收之前被回收。弱引用的对象将会被置为null。我们可以通过判断弱引用对象是否已经为null,来进行相关的操作。在ThreadLocalMap中,如果键ThreadLocal已经被回收,说明ThreadLocal对象已经为null,所以其对应的值已经无法被访问到。这时候就会造成内存泄漏了,为什么要造成内存泄漏呢??? 其实是故意而为之的我觉得,因为这样每次get()、set()、remove()就可以针对key为null的entry对象来进行清除了。这其实是在防止oom内存溢出,因为如果一直作为弱引用的话,那么线程一直保持着对threadlocalmap的引用,而threadlocalmap一直保持着对集合内部对象的引用。一直都不会释放。

再贴上内存泄漏和内存溢出的定义就清晰多了

  • 内存泄漏 memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
  • 内存溢出 out of memory :没内存可以分配给新的对象了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值