ThreadLocal直译为“本地线程”,但如果你真那么认为,那就错了!其实,ThreadLocal就是一个容器,用于存放线程的局部变量。它应该叫做ThreadLocalVariable(线程局部变量)才对。
在JDK1.2开始,java.lang.ThreadLocal就诞生了,是为了解决多线程并发问题而设计的,只是设计的有些难用,所以至今没有得到广泛的使用。
ThreadLocal的示例:
1. 先看不用ThreadLocal会有什么问题。
需求:一个多线程获取序列号的程序,多线程互不干扰,对于每个线程,获取完序列号后,序列号自动加1.
public interface Sequence {
int getNumber();
}
写一个线程类,用于获取序列号三次,每次获取后,序列号自动加1
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;
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-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9
可以看到,由于number是static的,所以他是被多个线程所共享的。
那么,如何做到不同的线程可拥有自己的static变量呢?
public class SequenceB implements Sequence {
private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
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();
}
}
这样,通过ThreadLocal封装了一个Integer类型的numberContainer静态成员变量,并且初始值是0。再看getNumber()方法,首先从numberContainer中get出当前的值,然后+1,在set回numberContainer中,然后将numberContainer中get出当前的值,并返回。
是不是很恶心,但的确很强大。确实稍微绕了一下,我们不妨把ThreadLocal看成是一个容器,这里,故意用container这个单词作为后缀来命名ThreadLocal变量。
运行结果如下:
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
这样每个线程互相独立了,同样是static变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样就保证了线程安全。也就是说,ThreadLocal为每个线程提供了一个独立的副本。
注意:当您在一个类中使用了 static 成员变量的时候,一定要多问问自己,这个 static 成员变量需要考虑“线程安全”吗?(也就是说,多个线程需要独享自己的 static 成员变量吗?)如果需要考虑,那就请用 ThreadLocal 吧!