ThreadLocal直译为"本地线程",但它并不是这样。ThreadLocal本身其实是一个容器,用于 存放线程的局部变量,这个类能使线程中的某个值与保存值的线程对象关联起来。ThreadLocal提供了get和set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份副本,因此get总是返回当前执行线程在调用set时设置的最新值。下面将以实例代码说明。
场景:一个序列号生成器的程序同时会有多个线程访问它,要保证每个线程得到的序列号都是自增的,而相互不干扰。
先定义一个接口:
public interface Sequence {
int getNumber();
}
每次调用getNumber()方法可获取序列号,下次在调用时,序列号会自增。
线程类
public class ClientThread implements Runnable {
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++;
return number;
}
public static void main(String[] args) {
Sequence sequence = new SequenceA();
ClientThread clientThread = new ClientThread(sequence);
ClientThread clientThread1 = new ClientThread(sequence);
ClientThread clientThread2 = new ClientThread(sequence);
new Thread(clientThread).start();
new Thread(clientThread1).start();
new Thread(clientThread2).start();
}
}
运行结果:
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-1 => 4
Thread-1 => 5
Thread-1 => 6
Thread-2 => 7
Thread-2 => 8
Thread-2 => 9
因为number是static修饰,属于共享资源,所以number一直自增,不能保证线程安全。
使用ThreadLocal
public class SequenceB implements Sequence {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
@Override
public int getNumber() {
threadLocal.set(threadLocal.get() + 1);
return threadLocal.get();
}
public static void main(String[] args) {
Sequence sequence = new SequenceB();
ClientThread clientThread = new ClientThread(sequence);
ClientThread clientThread1 = new ClientThread(sequence);
ClientThread clientThread2 = new ClientThread(sequence);
new Thread(clientThread).start();
new Thread(clientThread1).start();
new Thread(clientThread2).start();
}
}
运行结果
Thread-0 => 1
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-0 => 2
Thread-0 => 3
从运行结果可以看出,每个线程相互独立了,同样是static变量,对于不同的线程而言,它没有被共享,而是每个线程各一份,这样就保证了线程安全。也就是说,ThreadLocal为每个线程提供了独立的副本。
ThreadLocal的API
public void set(T value):将值放入线程局部变量中;
public T get():从线程局部变量中获取值;
public void remove():从线程局部变量中移除值;
protected T initialValue():返回线程局部变量中的初始值(默认为null)
自己实现ThreadLocal
ThreadLocal其实就是封装了一个线程安全的Map
public class MyThreadLocal<T> {
private Map<Thread, T> threadTMap = Collections.synchronizedMap(new HashMap<Thread, T>());
public void set(T t) {
threadTMap.put(Thread.currentThread(), t);
}
public T get() {
Thread thread = Thread.currentThread();
T value = threadTMap.get(thread);
if (value == null && !threadTMap.containsKey(thread)) {
value = initValue();
threadTMap.put(thread, value);
}
return value;
}
public void remove() {
threadTMap.remove(Thread.currentThread());
}
protected T initValue() {
return null;
}
}
使用MyThreadLocal来实现:
public class SequenceC implements Sequence {
private static MyThreadLocal<Integer> myThreadLocal = new MyThreadLocal<Integer>(){
@Override
protected Integer initValue() {
return 0;
}
};
@Override
public int getNumber() {
myThreadLocal.set(myThreadLocal.get() + 1);
return myThreadLocal.get();
}
public static void main(String[] args) {
Sequence sequenceC = new SequenceC();
new Thread(new ClientThread(sequenceC)).start();
new Thread(new ClientThread(sequenceC)).start();
new Thread(new ClientThread(sequenceC)).start();
}
}
运行结果:
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
运行效果和之前一样,同样正确。
ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如:在单线程应用程序中可能维持一个全局的数据库连接,并在程序启动时初始化这个连接,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会有属于自己的来连接。