浅析ThreadLocal

对于刚接触多线程的初学者来说,会发现很多框架都出现过ThreadLocal的身影,知道这个类是为了避免在多线程条件下出现资源竞争的问题。但是,在没有深入了解线程之前,更多的是知道ThreadLocal的目的是什么,对于是什么、怎么用、运行原理相信都是一个很模糊的概念,我希望这篇博客能够揭开困扰大家已久的谜团,以及带给大家一些相关知识。

说到要分析东西的时候,我觉得有一个比较好的方法可以帮助大家:不管分析什么,框架也好、源码也好,首先要对这个东西有一个概况了解、知道它是做什么的。千万不要一上来就看的很细很细,这样容易把自己绕晕而且效率很低(血的教训,哭);如果是分析JDK源码,尤其是集合框架的时候,可以先弄懂它的底层数据结构是什么,把底层数据结构弄懂了,那么分析这个类只是水到渠成的事情。哈哈

实现原理

其实就像我上面所说的那样,只要对线程有了一个深入的了解认识以后,就会很容易的理解ThreadLocal。

我们先用通俗的话解释一下ThreadLocal是一个什么东西,一句话:通过ThreadLocal保存的对象将在每一个使用它的线程留存一个副本,从而避免资源竞争,以空间换时间。

我们很自然的会有这样的疑问:
1. ThreadLocal是如何将对象保存在各个线程中的呢?
2. ThreadLocal又是如何获取保存在线程本地的对象的呢?

其实,我们可以这样猜:Thread应该有一个Map,保存了ThreadLocal维护的对象。
事实上ThreadLcoal和Thread也是这么做的:
1. Thread类持有了ThreadLcoal类中有一个内部类ThreadLocalMap,通过将对象保存在这个Map中来实现每个线程都持有一个对象副本;
2. 以ThreadLocal为Key,进行Key/Value存取操作。

ThreadLocal.java

//我们可以清晰看到get方法的逻辑:1、通过当前线程获取ThreadLocalMap;2、通过this(ThreadLocal)获取线程本地对象
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();
}
//set方法的逻辑很简单,如果map存在则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);
}

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

    }
}

Thread类中则持有了这个map,
Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

如何工作

我们最关心的还是如何使用ThreadLocal,与使用息息相关的就是set()和get()方法,我们把上面的代码拿过来再用一下:


public T get() {
    //获取当前线程,用于获取ThreadLocalMap
    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;
        }
    }
    //如果为map == null,则执行setInitialValue()方法,进行初始化
    return setInitialValue();
}

//设置初始值,逻辑与get()方法类似
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

//初始化副本,注意:这是一个protected方法,子类必须实现这个方法以生成副本
protected T initialValue() {
    return null;
}

//set方法的逻辑很简单,如果map存在则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);
}

代码中的注释应该比较详细,这里就不写了~
如果有不懂的,欢迎留言~

WeakReference

WeakReference是干啥的

细心的同学肯定发现了ThreadLocalMap的内部类Entry继承了WeakReference:

static class Entry extends WeakReference<ThreadLocal<?>>

免不了想问WeakReference是干啥的?
我们可以很容易联想到Java中的四种引用关系,而WeakReference对应的是弱引用关系;
我们来看WeakReference的doc中第一句话,

Weak reference objects, which do not prevent their referents from being
 made finalizable, finalized, and then reclaimed. 

 弱引用不会阻止它引用的对象变成finalizable、finalized状态并最终被回收

那,Entry为什么要继承WeakReference呢?

Entry为什么要继承WeakReference

如果线程太多(如线程池中的线程),那么保存的副本将会很多,而且不能回收。
因为thread没有运行结束那么会一直存在不能被回收,而threadLocals与对象副本之间是强引用的关系,所以这些副本也就不会被回收。大多数thread都是在阻塞等待状态,真正运行的thread只是少数,所以这些阻塞等待状态的thread所保存的副本就白白浪费了宝贵的内存资源。为了解决这个问题,JDK让Entry类继承了WeakReference类,这样当JVM进行GC的时候可以回收Thread保存的副本。这就意味着,副本对象必须是易于创建的,状态简单的对象,如数据库连接Connection。

Java中的四种引用

  1. 强引用
    只要强引用的存在,垃圾回收器永远不会回收这个对象;
  2. 软引用(SoftReference)
    当内存不足时,垃圾回收器将会回收被软引用引用的对象;
  3. 弱引用(WeakReference)
    垃圾回收器将会在下一次GC时回收被弱引用引用的对象
  4. 虚引用(PhantomReference)
    虚引用的存在与否完全不会影响垃圾回收器回收这个对象,虚引用存在的意义是对象被回收时发出一个通知。

扩展阅读

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值