ThreadLocal学习笔记
Posted *^O^*—*^O^*
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal学习笔记相关的知识,希望对你有一定的参考价值。
概念
ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static类型变量。
ThreadLocal 的作用和同步机制有些相反:同步机制是为了保证多线程环境下数据的一致性;而ThreadLocal 是保证了多线程环境下数据的独立性。
简单使用
public class Test
private static String commStr;
private static ThreadLocal<String> threadStr = new ThreadLocal<String>();
public static void main(String[] args)
commStr = "main";
threadStr.set("main");
Thread thread = new Thread(new Runnable()
@Override
public void run()
commStr = "thread";
threadStr.set("thread");
);
thread.start();
try
thread.join();
catch (InterruptedException e)
e.printStackTrace();
System.out.println(commStr);
System.out.println(threadStr.get());
从运行结果可以看出,对于 ThreadLocal 类型的变量,在一个线程中设置值,不影响其在其它线程中的
值。也就是说 ThreadLocal 类型的变量的值在每个线程中是独立的。
实现
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(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。
ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。
get
public T get()
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
return setInitialValue();
在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。
remove
public void remove()
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
ThreadLocalMap
在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操
作。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自
己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。
构造方法
ThreadLocal 中当前线程的 ThreadLocalMap 为 null 时会使用 ThreadLocalMap 的构造方法新建一个
ThreadLocalMap:
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);
构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。
存储结构
ThreadLocalMap 内部维护了一个哈希表(数组)来存储数据,并且定义了加载因子:
// 初始容量,必须是 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;
table 是一个 Entry 类型的数组,Entry 是 ThreadLocalMap 的一个内部类。
存储对象Entry
Entry 用于保存一个键值对,其中 key 以弱引用的方式保存:
static class Entry extends WeakReference<ThreadLocal<?>>
Object value;
Entry(ThreadLocal<?> k, Object v)
super(k);
value = v;
获取Entry对象
private void remove(ThreadLocal<?> key)
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)])
if (e.get() == key)
e.clear();
expungeStaleEntry(i);
return;
内存泄露
在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除无效 Entry 的操作,这样做是为了降
低内存泄漏发生的可能。
Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
假设 Entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 ThreadLocalMap 的生命周期和当
前线程一样长,那么当引用 ThreadLocal 的对象被回收后,由于 ThreadLocalMap 还持有 ThreadLocal
和对应 value 的强引用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以
Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法
回收的,依然会导致内存泄漏。
以上是关于ThreadLocal学习笔记的主要内容,如果未能解决你的问题,请参考以下文章