ThreadLocal使用示例
/**
* ThreadLocal 使用示例
*/
public class Test02 {
public static void main(String[] args) {
NumCounter numCounter1 = new NumCounter();
NumCounter numCounter2 = new NumCounter();
numCounter1.start();
numCounter2.start();
}
}
class NumCounter extends Thread {
//推荐写法,与GC有关,避免内存泄漏
private static ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);
@Override
public void run() {
for (int i = 0; i < 3; i++) {
addNum();
System.out.println(counter.toString() + " "
+ currentThread().getName() + " numCounter: [" + getNum() + "]");
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public Integer getNum() {
return counter.get();
}
public void addNum() {
counter.set(counter.get() + 1);
}
}
ThreadLocal两个主要方法:
-
T get()
获取值; -
void set(T value)
更新值;
看一下程序运行结果:
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-0 numCounter: [1]
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-1 numCounter: [1]
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-1 numCounter: [2]
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-0 numCounter: [2]
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-1 numCounter: [3]
java.lang.ThreadLocal$SuppliedThreadLocal@2fd04fd1 Thread-0 numCounter: [3]
可以看到,两个线程的ThreadLocal内的值互不影响,都是3
。
但是,ThreadLocal的修饰符是private static
,这代表该静态变量是线程共享的,从结果也可以看出,两个线程使用的是同一个实例@2fd04fd1
。这意味着ThreadLocal内的值与ThreadLocal本身的实例无关。
从源码分析
我们从两个主要方法出发:
get()
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
//=1= 通过当前Thread 实例,获取到一个ThreadLocalMap 实例
ThreadLocalMap map = getMap(t);
if (map != null) {
//=2= 通过this-即ThreadLocal实例 , 获取到一个Entry ,从名字来看与Map中实现类似
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//获取值
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
看完get
有以下疑问:
- =1= 中,
ThreadLocalMap
是什么?在哪个类定义并实例化? - =2= 中,
Entry
是否和Map
中的类似,是个K/V
形式的数据结构?
我们继续看get()
中的getMap()
方法:
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
哦!ThreadLocalMap
是Thread
类中的一个成员变量:
而ThreadLocalMap
是ThreadLocal类的一个静态内部类:
ThreadLocalMap.Entry
和预想的一样,是以ThreadLocal
为key,Object
为value的键值对。
所以我们最初的疑问有如下解释:
- 每一个
Thread
维护了一份独有的ThreadLocalMap
。(坐实) - 而
ThreadLocal
只是作为该ThreadLocalMap
的键。(坐实) - 调用
ThreadLocal
的set()
方法,其实是去修改Thread
内ThreadLocalMap
中的键值对的值。(猜想)
验证猜想:
set()
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
//获取到当前Thread的ThreadLocalMap 实例
ThreadLocalMap map = getMap(t);
if (map != null)
//猜想坐实
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal的作用
官方解释:
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
个人理解:
让开发者,更“优雅”地在线程内使用局部变量。(爱用不用)