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()); } }
运行结果:
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 }
(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中。 }
(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; }
(4)remove方法
源码如下:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread());//获取当前线程的ThreadLocalMap if (m != null)//不为null,则移除 m.remove(this); }
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); }
构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。
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; }
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; } }
Entry用于保存一个键值对,其中 key以弱引用的方式保存。
(4)内存泄露
在ThreadLocalMap的set、get、remove方法中,都有清除无效Entry的操作,这样做是为了降低内存泄漏发生的可能。
Entry中的key使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
注意:使用ThreadLocal的时候,每次用完ThreadLocal都调用remove方法,清除数据,防止内存泄漏。