Java ThreadLocal 类的知识点解读

提及 Java 中的 ThreadLocal 类,可能不少安卓开发人员并非很熟悉,毕竟不多有使用到的地方。可是若是你仔细分析过 Handler 源码的话,就必定见过这个类的出现。而 Handler 机制又是安卓知识体系中很是重要的一环,因此咱们有必要了解一下 ThreadLocal 类的相关知识点。java

ThreadLocal 使用简介


顾名思义,ThreadLocal 必定与线程有关,而事实也是如此。ThreadLocal 做为一个泛型类,解决的是线程内部对象访问的问题,必定程度上避免对象做为参数处处传递。一个线程经过 ThreadLocal 保存的泛型对象实例,其余线程没法访问,固然也无需访问。程序员

ThreadLocal 提供两个对外方法:get() 和 set() 方法,分别用于内部对象的读写操做。编程

举个例子,在工做线程中建立 ThreadLocal 对象,并存储和读取其字符串内容:微信

new Thread(new Runnable() {
    @Override
    public void run() {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("This is thread local variable");
        System.out.print(threadLocal.get());
    }
}).start();复制代码

若是将上面例子中的建立语句提取到工做线程外面的其余线程中,编译器会自动报错,提示访问受限:多线程

Variable "threadLocal" is accessed from within inner class,needs to be declared final并发

ThreadLocal 工做原理


搞清楚 ThreadLocal 内部工做原理,能够从前面使用简介中提到的 get() 和 set() 两个方法的源码入手。ide

get() 方法源码以下:oop

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}复制代码

前面提到,ThreadLocal 与线程有关,从 get() 方法的源码中便有所体现。使用当前线程实例做为 getMap() 方法的参数,直接获取用于保存 ThreadLocal 值的 ThreadLocalMap 对象。ui

getMap() 方法源码很简单,只有一行代码:this

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}复制代码

即直接读取 Thread 类中定义的 ThreadLocalMap 变量:

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;复制代码

再回到 get() 方法源码中,若是当前线程 threadLocals 变量的值为 null,则调用 setInitialValue() 方法进行初始化:

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;
}复制代码

也就是间接调用 createMap() 方法实现初始化操做:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}复制代码

而且初始化时使用的默认值是由 initialValue() 方法提供的默认值 null:

protected T initialValue() {
    return null;
}复制代码

该方法为 protected 类型,这也为开发人员使用时修改系统默认值 null 提供了可能。咱们只须要在建立 ThreadLocal 实例的时候或者在其子类中重写 initialValue() 方法便可,好比:

ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "a new initial value";
    }
};复制代码

看完 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() 方法一样获取的是当前线程中的 ThreadLocalMap 对象进行赋值操做。其中有一行代码:

map.set(this, value);复制代码

与 get() 方法中提到的初始化操做同样,表示 ThreadLocalMap 使用 ThreadLocal 实例做为 key、将要保存的对象做为 value 这种键值对的形式保存。而 get() 方法读取的时候也是如此。

Android 中的应用场景


做为安卓开发人员,使用 ThreadLocal 的场景并非不少,然而咱们平时常常接触的 Handler 机制中涉及到的 Looper 类,其内部便使用到 ThreadLocal 操做。

你们知道,一个线程中只能有一个 Looper 实例不停循环运转访问 MessageQueue 消息队列。这个 Looper 对象实例即是借助 ThreadLocal 保存在对应线程当中,Looper 类源码定义以下:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();复制代码

默认状况下,工做线程中使用 Looper 以前必须调用 prepare() 方法初始化(系统在主线程自动帮咱们执行)。这个方法即是用来建立 Looper 对象并保存的:

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}复制代码

因此,知道一些 ThreadLocal 的知识有助于咱们加深理解 Handler 原理。

ThreadLocal 答忧解惑


1,ThreadLocal 只能在线程内访问吗

其实否则。Java 还提供有一个继承自 ThreadLocal 的 InheritableThreadLocal 类,解决跨线程访问 ThreadLocal 变量的问题。

在 Thread 类中也定义有一个 inheritableThreadLocals 变量:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;复制代码

当子线程建立的时候,自动使用父线程中 inheritableThreadLocals 变量保存的对象值初始化本身。(固然,该默认值也是能够由开发人员修改的。)

private void init2(Thread parent) {
    // 省略不相关源码
    if (parent.inheritableThreadLocals != null) {
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
                parent.inheritableThreadLocals);
    }
}复制代码

有关 InheritableThreadLocal 的更多细节,感兴趣的朋友能够自行查阅相关源码。

2,ThreadLocal 会引起内存泄漏吗

答案是否认的。虽然从前面的源码中能够看出,ThreadLocal 保存的对象实例本质上归属于 Thread 所持有引用,当线程执行完毕,GC 自动回收其相关资源。

看上去,默认状况下对象的生命周期与 Thread 生命周期一致,可是,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;
        }
    }复制代码

备注:对于 JVM 来讲,内存泄漏的罪魁祸首在于强引用致使对象所占用的内存没法获得释放,而 Java 四种引用类型中的弱引用方式恰好可以解决这种问题。弱引用不会影响 GC 的垃圾回收工做,避免内存泄漏风险。同时也应当注意,使用弱引用获取到的对象实例,在使用前,须要添加空值判断。

3,ThreadLocal 与多线程并发的关系?

没有关系!网上有些文章误传 ThreadLocal 是用来解决多线程并发的问题,这是错误的理解。经过前文一系列的使用和原理分析不难看出,ThreadLocal 用于线程读写各自内部对象,一般除本身以外的线程没有必要也没法访问这个对象。

而多线程并发产生的临界资源访问问题,彻底能够经过 synchronized 关键字实现的同步机制(也称互斥锁机制)解决。有关 Java 并发编程的知识点,能够参考:

wiki.jikexueyuan.com/project/jav…

相关知识链

dzone.com/articles/st…

关于我:亦枫,博客地址:yifeng.studio/,新浪微博:IT亦枫

微信扫描二维码,欢迎关注个人我的公众号:安卓笔记侠

不只分享个人原创技术文章,还有程序员的职场遐想

相关文章
相关标签/搜索