ThreadLocal可以理解为一个工具类,能够为多个线程提供保存在各个线程内部的局部变量,通过提供的set和get方法来实现线程独立地对这些变量的修改和访问。因为变量保存在线程内部,线程在操作时不会产生线程不安全问题。
以下是简单的使用示例,两线程都对变量赋不一样的值,分别访问,删除
public class T07_ThreadLocal { static ThreadLocal<String> local = new ThreadLocal<>(); public static void print() { local.get(); System.out.println(Thread.currentThread().getName() + local.get()); } public static void remove() { local.remove(); } public static void main(String[] args) { Thread t1 = new Thread(() -> { local.set("Hello"); print(); remove(); System.out.println("thread-1 after remove"); print(); }, "thread-1"); Thread t2= new Thread(() -> { local.set("Bye Bye"); print(); remove(); System.out.println("thread-2 after remove"); print(); }, "thread-2"); t1.start(); t2.start(); } }输出结果
ThreadLocal向外提供了三个方法(set,get,remove),完成线程对局部变量的操作:
set(T value):将此局部变量当前线程的副本设定为指定值。
get():获取当前线程副本的值;
remove():移除当前线程副本的值;
源码:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 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(); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }通过源码我们可以得知,三个方法都要调用getMap方法,获取当前线程的threadLocals,是一个ThreadLocalMap对象,所有操作都是对线程持有的threadLocals的维护(增删改查)。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } ThreadLocal.ThreadLocalMap threadLocals = null;问题来了,ThreadLocalMap是什么,里有有哪些东西呢?
以下是ThreadLocalMap的主要构成。
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; ... }ThreadLocalMap中有一个内部类Entry,姑且可以将其理解为类似hashMap样的东西,并且这个map的是以当前ThreadLocal对象作为key,同时也定义了一个Entry数组,也就是说,一个线程内有一块连续空间,存有一堆key-value。
以ThreadLocalMap的set方法为例,可以进一步理解
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //计算出当前entry所在数组的下标 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //获取key,当前ThreadLocal对象 ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }执行逻辑如下:
根据hash算法,计算出当前ThreadLocal对象所在Entry数组的下标;将要设置的value设置对该位置的Entry的value;ThreadLocal和synchronize关键字一样,用于解决多线程之间的冲突,synchronize是通过加锁的方式,每个线程依次对某一变量的访问,执行时间较长,而ThreadLocal则是以空间换时间,给每个线程都保有一个变量的副本,减少线程额外的开销。
还有值得注意的是,Entry是继承自弱引用,当ThreadLocal为null时,会被GC回收,然而在ThreadLocalMap中Entry数组的key为null,value却是有值,从而造成内存泄漏。故此,在使用完ThreadLocal后,需要执行remove方法,以避免内存泄漏。