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 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。
posted @ 2021-05-11 15:06  莫待樱开春来踏雪觅芳踪  阅读(244)  评论(0)    收藏  举报