zoukankan      html  css  js  c++  java
  • ThreadLocal深入理解

    大致理解

    顾名思义,ThreadLocal指的就是Thread的本地文件,即每一个线程专属的本地变量。

    详细定义如下:

    ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

    因此,当一个实例需要在多个线程中使用,且每个线程都希望该实例不会被其他线程说使用,这种情况下把实例放入ThreadLocal类中将会十分方便。常用场景有数据库链接和Session管理等。

    深入分析

    说完了基本意义和使用场景后,接下来需要分析ThreadLocal类的内部构造,明白其是如何实现的。

    观察ThreadLocal类的内部结构后发现其内部包含了ThreadLocalMap的内部类,该map中的key为ThreadLocalMap的弱引用,value储存了所需的实例副本,其中ThreadLocalMap由每个线程自行维护。

    static class Entry extends WeakReference<ThreadLocal> {
        Object value;
    
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
    

    初始化实例

    当线程中ThreadLocalMap为空时,线程便会初始化map,如果存在map就会将实例放入map中。

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

    其中ThreadLocalMap是每个线程单独维护的,都是取自于Thread类中的threadLocals变量。

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

    需要注意的是,如果没有重写initialValue方法,实例默认为null

    protected T initialValue() {
        return null;
    }
    

    获取实例

    get方法首先获取了当前线程对象,然后通过getMap方法获取了当前线程中的ThreadLocalMap,接着以ThreadLocal为key寻找到对应的实例,最后判断实例是否为空,如果不为空直接返回,如果为空则初始化后再返回。

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

    设置实例

    设置实例与获取实例相似,也是获取当前线程中的ThreadLocalMap,然后以ThreadLocal为key设置其value

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

    内存泄漏

    分析完了ThreadLocal相关源码后,将其内存模型构建出来。

    从内从模型可以看到ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal没有外部强引用时这会被GC回收,不过Entry中的 value因为被 ThreadLocalMap所引用,因此就会出现keynull的Entry。这种情况下将导致value无法引用并不能回收,造成内存泄漏。其实,ThreadLocalMap设计时已经考虑到这种情况,每当调用ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

    不过错误的代码编写难免会导致内存泄漏的发生,常见的有:

    • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。
    • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。

    因此,每次使用完ThreadLocal,都需要调用其remove()方法清除数据,避免内存泄漏。

    参考文献

    深入分析 ThreadLocal 内存泄漏问题
    Java并发编程:深入剖析ThreadLocal
    Java进阶(七)正确理解Thread Local的原理与适用场景

  • 相关阅读:
    阶段1 语言基础+高级_1-2 -面向对象和封装_2面向对象思想的举例
    阶段1 语言基础+高级_1-2 -面向对象和封装_1面向对象思想的概述
    2-3 【初识组件】顶部 TabBar
    2-2 工程源码文件结构
    Fragment状态保存
    【51单片机】六种亮灯方式
    Hadoop自学笔记(二)HDFS简单介绍
    lvs 负载均衡环境搭建
    [学习笔记—Objective-C]《Objective-C-基础教程 第2版》第十一章 属性
    说说nio----1
  • 原文地址:https://www.cnblogs.com/gforce/p/9323694.html
Copyright © 2011-2022 走看看