0. ThreadLocal简单使用
- ThreadLocal类的简单使用。下面(程序中)共有 10 个 ‘人’ 同时命令 ‘小李子’ 做点什么事情 : - ),代码如下(使用 ThreadLocal 完成统计方法的执行时间):
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Temp_4 {
public static void main(String[] args) throws InterruptedException {
Person person = new Person("小李子");
System.out.println(person.getName() + " begin to do");
int threadNum = 10;
Thread[] workers = new Thread[threadNum];
for(int i = 0;i < threadNum;i++) {
workers[i] = new Thread(new Runnable() {
@Override
public void run() {
person.toDoSomething();
}
}, "t-" + i);
}
for(int i = 0;i < threadNum;i++) {
workers[i].start();
}
for(int i = 0;i < threadNum;i++) {
workers[i].join();
}
System.out.println("all works done.");
}
}
class Person {
private static ThreadLocal<Long> costTime = new ThreadLocal<Long>();
private static Random random = new Random();
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void toDoSomething() {
costTime.set(System.currentTimeMillis()); // 上班打卡
String threadName = Thread.currentThread().getName();
int sleepTime = random.nextInt(1001);
while(sleepTime == 0) {
sleepTime = random.nextInt(1001);
}
try {
TimeUnit.MILLISECONDS.sleep(sleepTime); // 小李子只想睡觉
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打卡下班
System.out.println(threadName + " command " + name
+ " to do something, cost time is "
+ (System.currentTimeMillis() - costTime.get()) / 1000.0 + "s");
}
}
执行结果如下:
小李子 begin to do
t-1 command 小李子 to do something, cost time is 0.083s
t-2 command 小李子 to do something, cost time is 0.193s
t-6 command 小李子 to do something, cost time is 0.396s
t-9 command 小李子 to do something, cost time is 0.41s
t-7 command 小李子 to do something, cost time is 0.461s
t-5 command 小李子 to do something, cost time is 0.488s
t-3 command 小李子 to do something, cost time is 0.494s
t-8 command 小李子 to do something, cost time is 0.575s
t-4 command 小李子 to do something, cost time is 0.668s
t-0 command 小李子 to do something, cost time is 0.803s
all works done.
查看程序的运行结果,从中我并没有看出什么(-_- !),所以还是去看一眼 ThreadLocal 类的具体实现。(猜想:我们知道,ThreadLocal 可以实现:为每个线程保存一个对象,所以我猜测它的内部必定有一个数据结构/容器来维护每个线程及其持有的对象,该数据结构/容器要保证多线程安全,而且当线程终止后,ThreadLocal 最好还能够释放对该线程及其所持对象的引用)
1. ThreadLocal 类的具体实现
- 从源代码开始。ThreadLocal 对外暴露的可供我们使用的方法总共只有 3 个:
该类仅仅只有一个无参的构造器方法,且该方法的方法体为空。
先看 get 方法,该方法返回 ThreadLocal 对象实例关联的 value 值(这里使用泛型,所以我们可以为线程保存任意的对象实例),get 方法源代码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
其中 ThreadLocalMap 为一个自定义的 hashmap 实现(暂时不要关注它的实现细节),其目的是存放键值对 <ThreadLocal,value> 即每个 ThreadLocal 实例关联一个值 value,且线程类 Thread 中声明了一个 ThreadLocalMap 类型属性,用于保存属于该线程的 ‘线程本地变量’。我们知道,对于任一线程,一个 ThreadLocal 对象只关联一个值,而一个线程可以拥有多个 ThreadLocal 实例,所以就可以清晰:每个线程都有一个 hashmap 即 ThreadLocalMap 实例,用来保存属于该线程的 ThreadLocal 实例以及与该 ThreadLocal 实例对应的值 value。注意,每个线程都在一个属于它自己的 hashmap 对象上进行( get/set)操作,所以这里并不会有线程安全问题。当多个线程使用/操作同一个 ThreadLocal 实例时,相当于:有多个 hashmap 实例,使用同一个对象(即 ThreadLocal 实例)作为 key,来关联它们各自的 value 值。
综上,我是为了了解 ThreadLocal 的实现机制,所以从 ThreadLocal 类本身开始下手,但是直到看 ThreadLocalMap 以及线程类 Thread 中的 ThreadLocalMap 类型的属性域,我才恍然大悟:我搞错了顺序,我一开始就进入到了最里面,从而困惑于 ThreadLocal 的实现细节。我认为对 ThreadLocal 的理解,应当从 Thread 类中的 ThreadLocalMap 类型的属性开始:每个线程都有个一 ThreadLocalMap 类型的 hashmap 域,用于维护以 ThreadLocal 对象为 key 并以任意对象为 value 的键值对。所以再看 ThreadLocal,其仅仅用于创建一个普通的对象,该对象没有状态,只是作为 hashmap 的 key 而存在,至于它被多个线程使用,这一点并不特别。(还有一点就是 ThreadLocal 类本身,我认为这个类被设计的比较复杂,因为该类内部还隐藏了其它 3 个类,最关键的就是 ThreadLocalMap 类了,ThreadLocalMap 以它的包装类 ThreadLocal 作为 key,其自身却藏在 ThreadLocal 里面,可见类设计者有意隐藏 ThreadLocal 的实现细节,使得可以像使用普通对象一样对待 ThreadLocal 实例。注意,ThreadLocalMap 虽然定义在 ThreadLocal 的内部,但是它被定义为内部静态类,这和单独定义它其实效果一样,这一点可以简化对 ThreadLocal 的理解。)