[TOC]
前言:ThreadLocal线程局部变量,为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。即“以空间换时间”的方式解决线程不安全问题。
本文首先对ThreadLocal进行源码分析以阐明其原理,而后介绍ThreadLocal重要的三个知识点:它用来解决冲突的办法——线性探测法、它用来解决内存泄露的办法——弱引用、ThreadLocal的清理时机尤其要注意线程池这个例外。
源码分析
原理
ThreadLocal中有一个静态内部类ThreadLocalMap(类似于HashMap但不是)。这个Map的key是ThreadLocal当前对象,value就是我们存起来的值。
下面通过分析重要方法的源码来一探究竟。
getMap()
取得的是当前线程的ThreadLocalMap
1 | ThreadLocalMap getMap(Thread t) { |
注意:这个map取得的是Thread类的成员变量ThreadLocalMap
1 | class Thread implements Runnable { |
set()
取当前线程的ThreadLocalMap,key设置为ThreadLocal当前对象,value设置为传入的值
1 | public void set(T value) { |
get()
取当前线程的ThreadLocalMap,将threadLocal作为key获取Entry后获取value。
1 | public T get() { |
线性探测法
ThreadLocalMap是典型的使用线性探测法解决hash冲突的:发生冲突,从该位置向后找到表中的下一个空槽放入。这种简单的方法会导致相同hash值的元素挨在一起和其他hash值对应的槽被占用。
1 | private void set(ThreadLocal<?> key, Object value) { |
弱引用
总所周知,只要还有引用指向,这个对象就不会被回收。这是针对强引用而言。如果一个类继承了软引用指向实例,如果这个实例没有其他引用了,只有该类引用了,这个对象会被GC立即回收。
1 | A a = new A(); |
如果b还有有用,不能赋值为空,岂非a一直不能被回收?非也,我们还可以用弱引用。
1 | A a = new A(); |
顺便说一嘴,软引用和弱引用一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时才会被回收. 正因为有这个特性, 软引用比弱引用更加适合做缓存对象的引用。
ThreadLocal使用了弱引用.key指向ThreadLocal实例,当ThreadLocal外部强引用被回收时候,key虽然还是指向ThreadLocal,但因为是弱引用,GC会发现并回收。
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
通过表格来说明一下,如下:
引用类型 | 被垃圾回收时间 | 用途 |
---|---|---|
强引用 | 从来不会 | 对象的一般状态 |
软引用 | 当内存不足时 | 对象缓存 |
弱引用 | 正常垃圾回收时 | 对象缓存 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 |
清理时机
线程退出时,会做一个清理工作,其中就包括清理ThreadLocalMap,即把threadLocals=null。
而然使用线程池会对线程进行复用,就意味当前线程未必会退出,可能会出现内存泄露,即你不用这个对象了,但它无法被回收。因此最好每次使ThreadLocal.remove()方法将这个变量移除。
参考
《Java高并发程序设计》