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

浙公网安备 33010602011771号