本文整理自《架构探险——从零开始写Java Web框架》一书。之前对ThreadLocal一直没怎么去了解,刚好这书里谈到了,就摘录下来,加深记忆。
什么是ThreadLocal
可以理解为一个容器,用于存放线程的局部变量,不能根据名称直译为“线程本地”或“本地线程”。把它理解成一个容器,才能比较容易理解它能够做的事情。
先看一个简单的例子:
public interface Sequence {
int getNumber();
}
public class ClientThread extends Thread {
private Sequence sequence;
public ClientThread(Sequence sequence) {
this.sequence = sequence;
}
@Override
public void run() {
for (int i=0;i<3;i++) {
System.out.println(Thread.currentThread().getName()+" => "+
sequence.getNumber());
}
}
}
public class SequenceA implements Sequence {
private static int number = 0;
@Override
public int getNumber() {
number = number+1;
return number;
}
public static void main(String[] args){
Sequence sequence = new SequenceA();
ClientThread thread1 = new ClientThread(sequence);
ClientThread thread2 = new ClientThread(sequence);
ClientThread thread3 = new ClientThread(sequence);
thread1.start();
thread2.start();
thread3.start();
}
}
此时,程序的运行结果为:
Thread-0 => 1
Thread-1 => 2
Thread-0 => 3
Thread-1 => 4
Thread-0 => 5
Thread-1 => 6
Thread-2 => 7
Thread-2 => 8
Thread-2 => 9
前面由于线程启动顺序是随机的,所以并不是0、1、2的顺序。后面出现1~9,而不是1~3、1~3…重复的顺序输出的原因是,线程之间共享的static变量无法保证对于不同线程而言是安全的,也就是说,此时无法保证“线程安全”,即3个线程分别进行的getNumber不断的修改了static变量number。
这里我们再用ThreadLocal来实现:
public class SequenceB implements Sequence {
private static ThreadLocal<Integer> numberContainer =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
@Override
public int getNumber() {
numberContainer.set(numberContainer.get()+1);
return numberContainer.get();
}
public static void main(String[] args){
Sequence sequence = new SequenceB();
ClientThread thread1 = new ClientThread(sequence);
ClientThread thread2 = new ClientThread(sequence);
ClientThread thread3 = new ClientThread(sequence);
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果:
Thread-0 => 1
Thread-1 => 1
Thread-0 => 2
Thread-1 => 2
Thread-0 => 3
Thread-2 => 1
Thread-1 => 3
Thread-2 => 2
Thread-2 => 3
可以看到,当使用ThreadLocal后,对于单个线程0、1、2,都可以按1~3的顺序输出了。所以相当于这个numberContainer静态变量没有被共享,而是每个线程各一份,这样就保证了线程安全。
这里顺便说一下ThreadLocal的API:
- public void set(T value):将值放入线程局部变量中;
- public T get():从线程局部变量中获取值;
- public void remove():从线程局部变量中移除值;
- protected T initialValue():返回线程局部变量中的初始值(默认为null)
自己实现ThreadLocal
这里简单山寨一个ThreadLocal,加深对ThreadLocal的理解:
public class MyThreadLocal<T> {
private Map<Thread,T> container = Collections.synchronizedMap(new
HashMap<>());
public void set(T value) {
container.put(Thread.currentThread(),value);
}
public T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if (value == null && !container.containsKey(thread)) {
value = initialValue();
container.put(thread,value);
}
return value;
}
public void remove() {
container.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
这个“山寨货”同样可以起到与使用ThreadLocal相同的结果。
技巧
当在一个类中使用了static成员变量的时候,一定要多问问自己,这个static成员变量需要考虑“线程安全”吗?也就是说,多个线程需要独享自己的static成员变量吗?如果需要考虑,不妨使用ThreadLocal。