概念
ThreadLoacl主要是用来做线程间共享但又不关联的共享变量的存储。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,也就是只是共用这个变量以及该变量的初始值,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLocalTest { public void test(){ ThreadLocal<Integer> local = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; } }; ExecutorService service = Executors.newFixedThreadPool(3); for(int i=0;i<3;i++){ service.execute(new Runnable() { @Override public void run() { Integer num = local.get(); Integer init = num; while(++num<10); System.out.println("当前线程为:" + Thread.currentThread().getName() + ",初始值为:"+init+",结果数据为:" + num); } }); } service.shutdown(); } public static void main(String[] args) { new ThreadLocalTest().test(); } }
当前线程为:pool-1-thread-1,初始值为:0,结果数据为:10 当前线程为:pool-1-thread-3,初始值为:0,结果数据为:10 当前线程为:pool-1-thread-2,初始值为:0,结果数据为:10
通过测试代码可以看到,虽然三个线程使用了同一个ThreadLocal变量,并各自进行加到10操作,但是三个线程之间并没有相互影响,而只是共享了变量初始值。
ThreadLocal源码分析
1. get()
首先查看ThreadLocal构造方法,发现什么也没做,那么查看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方法首先取得当前线程对象,接着借此调用getMap方法返回ThreadLocalMap对象。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }而getMap方法最终是从当前线程中取得ThreadLocalMap实例,查看线程类:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;发现它是线程类的实例成员,也就是说,每一个线程都有自己独立的ThreadLocalMap实例。
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }其中initialValue()方法就是我们重写过的方法,在上面的例子中我们返回的是0.由于是第一次执行,getMap(t)方法方的是null,所以接着看createMap方法:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }终于创建了当前线程t的ThreadLocalMap,其中参数this为当前ThreadLocal实例,firstValue为其初始值:
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构造方法中,创建了一个类似Map的对象,并且以ThreadLocal实例为key,initialValue()方法的返回值为value。所以每一个线程都有一个独立的这样的ThreadLocalMap对象将ThreadLocal实例与其值关联起来。 接下来,如果 第二次调用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(); }由于getMap(t)不再返回null,所以进入if块,然后以当前ThreadLocal实例为Key,取得当前线程的ThreadLocalMap中对应的值,当然如果发现不存在这样的键值对entry,那么还是进行执行setInitialValue. 2. set(T value) 这个方法就是重新设置每一个线程的本地ThreadLocal变量的值
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }这里取得当前线程,然后根据当前的thradLocal实例取得其map。然后重新设置 map.set(this, value);这时这个线程的thradLocal里的变量副本就被重新设置值了! 3. remove() 就是清空ThreadLocalMap里的key-value键值对,这样一来。下次再调用get时又会调用initialValue这个方法返回设置的初始值.
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }总结: 1、每个线程都有自己的局部变量 每个线程都有一个独立于其他线程的上下文ThreadLocalMap来保存这个变量。 2、独立于变量的初始化副本 ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。 3、状态与某一个线程相关联 ThreadLocal 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在, 而是为了方便每个线程处理自己的状态而引入的一个机制。