ThreadLocal类说明

1.简介

  ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。  

  ThreadLocal是一个关于创建线程局部变量的类。

  通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。相当于线程的private static类型变量

  ThreadLocal的优点:

  • 提供线程内的局部变量:每个线程都自己管理自己的局部变量,互不影响。

  ThreadLocal的缺点:

  • 内存泄露问题:在ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时value和value指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!

  ThreadLocal的使用场景:

  • 实现单个线程单例以及单个线程上下文信息存储,比如通用id等。
  • 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。
  • 承载一些线程相关的数据,避免在方法中来回传递参数。

2.使用示例

  示例代码如下

public class Test {
    private static String strLabel;
    private static ThreadLocal<String> threadLabel = new ThreadLocal<String>();
    public static void main(String[] args) throws Exception {
        strLabel = "开始";
        threadLabel.set("开始");
        new Thread(new Runnable() {
            public void run() {
                strLabel = "结束";
                threadLabel.set("结束");
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("strLabel = " + strLabel);
        System.out.println("threadLabel = " + threadLabel.get());
    }
}
View Code

  运行结果

strLabel = 结束

threadLabel = 开始

  从上面的示例可以看出,对于ThreadLocal类型的变量,在一个线程中设置值,不影响其在其它线程中的值。也就是说ThreadLocal类型的变量的值在每个线程中是独立的。

3.ThreadLocal的实现

  ThreadLocal是构造函数只是一个简单的无参构造函数,并且没有任何实现。下面说下ThreadLocal的方法。

(1)set方法

  源码如下

public void set(T value) {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
    if (map != null)
        map.set(this, value);//将value保存到 ThreadLocalMap中,并用当前ThreadLocal作为 key
    else
        createMap(t, value);//创建一个ThreadLocalMap并给到当前线程,然后保存 value
}
View Code

(2)get方法

  源码如下

public T get() {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//获取 key为当前 ThreadLocal的值
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();//调用setInitialValue()方法返回初始值,并保存到新创建的ThreadLocalMap中。
}
View Code

(3)setInitialValue方法

  源码如下

private T setInitialValue() {
    T value = initialValue();//设置ThreadLocal的初始值,默认返回 null
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
View Code

(4)remove方法

   源码如下

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());//获取当前线程的ThreadLocalMap
    if (m != null)//不为null,则移除
        m.remove(this);
}
View Code

4.ThreadLocalMap

  ThreadLocalMap是ThreadLocal的内部类。

(1)构造方法

  源码如下

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
View Code

  构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。

  ThreadLocal中当前线程的ThreadLocalMap为null时会使用ThreadLocalMap的构造方法新建。

(2)存储结构

   源码如下

// 初始容量,必须是 2 的幂
private static final int INITIAL_CAPACITY = 16;

// 存储数据的哈希表
private Entry[] table;

// table 中已存储的条目数
private int size = 0;

// 表示一个阈值,当 table 中存储的对象达到该值时就会扩容
private int threshold;

// 设置 threshold 的值
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}
View Code

  ThreadLocalMap内部维护了一个哈希表(数组)来存储数据,并且定义了加载因子。

  table是一个 Entry类型的数组,Entry是ThreadLocalMap的一个内部类。

(3)存储对象

    源码如下

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

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
View Code

  Entry用于保存一个键值对,其中 key以弱引用的方式保存。

(4)内存泄露

  在ThreadLocalMap的set、get、remove方法中,都有清除无效Entry的操作,这样做是为了降低内存泄漏发生的可能。

  Entry中的key使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。

  注意:使用ThreadLocal的时候,每次用完ThreadLocal都调用remove方法,清除数据,防止内存泄漏。

原文地址:https://www.cnblogs.com/bl123/p/14142650.html