Java中ThreadLocal的深入理解
官方对ThreadLocal的描述:
"该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或事物ID)相关联。"
《Thinking in Java》中的描述:
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。主要是,它们使得你可以将状态与线程关联起来。
三个要点:
1.每个线程都有自己的局部变量
每个线程都有一个独立于其它线程的上下文来保存这个变量,一个线程的本地变量对其它线程是不可见的。
2.独立于变量的初始化副本
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。
3.状态与某一个线程相关联
ThreadLocal不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,就像《Thinking in Java》中描述的那样:”它们使得你可以将状态与线程关联起来。“理解这点对正确使用ThreadLocal至关重要。
源码举例:
从输出的结果可以看出,五个线程处理自己的本地变量值。
而这个测试的结果,并没有相同。这是因为实例为对象,所以初始值为一个对象的引用,那么五个线程的副本就是这个对象的引用的副本,也就是说这些引用还是指向同一个对象,所以就出现了这种情况。
ThreadLocal的接口方法:
- void set(T value):设置当前线程的线程局部变量的值;
1 public void set(T value) { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 5 if (map != null) 6 map.set(this, value); 7 //说明线程第一次使用线程本地变量(注意这里的第一次含义) 8 else 9 createMap(t, value); 10 }
- public T get():该方法返回当前线程所对应的线程局部变量;
1 public T get() { 2 //获取当前执行线程 3 Thread t = Thread.currentThread(); 4 //取得当前线程的ThreadLocalMap实例 5 ThreadLocalMap map = getMap(t); 6 //如果map不为空,说明该线程已经有了一个ThreadLocalMap实例 7 if (map != null) { 8 //map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量 9 ThreadLocalMap.Entry e = map.getEntry(this); 10 //如果当前线程本地变量存在这个map中,则返回其对应的值 11 if (e != null) 12 return (T)e.value; 13 } 14 //如果map不存在或者map中不存在当前线程本地变量,返回初始值 15 return setInitialValue(); 16 }
- public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将被自动垃圾回收,所以显示调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
- protected T initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,这个方法是一个延迟调用方法,在线程第1次调用get()或set(T)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。
这里注意,ThreadLocal中是有一个Map,但这个Map不是我们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样做的好处是,每个线程都对应一个本地变量的Map,所以一个线程可以存在多个线程本地变量。下面提供一个简单的实现版本: