ThreadLocal 是干嘛的
简而言之, ThreadLocal 可以帮助你把某个值绑定到线程上
为什么要把某个值绑定到线程上呢?
我们来看个示例
public class Performance {
private static long beginTimeMillis;
public static final void begin() {
beginTimeMillis = System.currentTimeMillis();
}
public static final long end() {
return System.currentTimeMillis() - beginTimeMillis;
}
}
这段代码, 如果在单线程环境下使用没有问题, 但如果在多线程环境下使用, 是有问题的 !
如下
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
Performance.begin();
// doSomething();
long time = Performance.end();
System.out.println("Cost : " + time);
}
};
Thread t2 = new Thread() {
@Override
public void run() {
Performance.begin();
// doSomething();
long time = Performance.end();
System.out.println("Cost : " + time);
}
};
t1.start();
t2.start();
}
t1 线程有可能看到 t2 线程设置的 currentTimeMillis , 造成统计错误!
如何避免这种错误呢?
我们肯定希望, 在 t1 线程访问 Performance.begin() 的时候, 把 beginTimeMillis 和 t1 线程关联起来,
在 t2 线程访问 Performance.begin() 的时候, 把 beginTimeMillis 和 t2 线程关联起来,
这样, 当 t1 线程访问 Performance.end() 的时候, 我们就能知道 t1 线程的 beginTimeMillis ,
t2 线程访问 Performance.end() 的时候, 我们就能知道 t2 线程的 beginTimeMillis
从而可以计算出正确的结果!
如何把 beginTimeMillis 和线程进行关联呢 ? ThreadLocal !
ThreadLocal 的使用
ThreadLocal 的使用如下
public class Performance {
static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static final void begin() {
long beginTimeMillis = System.currentTimeMillis();
threadLocal.set(beginTimeMillis);
}
public static final long end() {
return System.currentTimeMillis() - threadLocal.get();
}
}
这样, 当 t1 线程 调用 Performance.begin() 时, beginTimeMillis 和 t1 关联了起来, t1 调用 Performance.end() 时, 通过 threadLocal.get() 取出了和 t1 关联的 beginTimeMillis, 从而可以计算出正确的结果.
ThreadLocal 原理
ThreadLocal 是怎么让线程和某个值关联起来的呢 ? 我们看下 Thread 类的源码
public class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread.
* This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
Thread 类中有一个名叫 threadLocals 的变量, 专门存储和本线程相关的一些值, 这个变量的类型是 ThreadLocalMap , 是一个集合, 我们只要操作这个变量, 就能让某些值和本线程关联起来.
为了便于我们操作 threadLocals 这个变量, JDK 专门提供了 ThrealLocal 这个类,
或者说为了让我们正确的使用 threadLocals 这个变量, JDK 提供了 ThrealLocal 这个类.
ThreadLocal 的使用上面已经说过了, 我们来看看threadLocal.set(beginTimeMillis);
是如何把值和当前线程关联起来的
ThreadLocal 的 set/get 方法伪代码如下
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
// this 是指 ThreadLocal 对象
map.set(this, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
ThreadLocalMap.Entry e = map.getEntry(this);
T result = (T)e.value;
return result;
}
可以看到, ThreadLocal 先取得访问当前方法的线程, 然后得到与线程相关的 threadLocals 变量. 然后操作 threadLocals 变量, 就把 线程和值关联起来了