ThreadLocal是什么
ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中"Local"索要表达的意思
ThreadLocal的接口方法
void set(T value);
T get();
protected T initialValue();
返回该线程局部变量的初始值,改方法时一个protected的方法,显然是为了让子类覆盖而设计的。这个方法时一个延迟调用方法,在线程第一次调用get()或set(T value)ThreadLocal默认实现直接返回一个null
ThreadLocal是如何做到为每一个线程维护一份独立的副本变量呢?其实实现思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应的变量副本,下面是一个简单版本的ThreadLocal
import java.util.HashMap;
import java.util.Map;
public class ThreadLocalSimple<T> {
private Map<Thread,T> threadLocalMap = new HashMap<>();
public void set(T object) {
threadLocalMap.put(Thread.currentThread(), object);
}
public T get() {
Thread currentThread = Thread.currentThread();
T object = threadLocalMap.get(currentThread);
if(object == null){
object = initialValue();
threadLocalMap.put(currentThread, object);
}
return object;
}
public void remove() {
threadLocalMap.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
一个ThreadLocal实例,通过这个实例来感受一下ThreadLocal的具体用法
public class SequenceNumber {
public static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public int getNextNum() {
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
TestClient task = new TestClient(sn);
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);
t1.start();
t2.start();
t3.start();
}
private static class TestClient implements Runnable {
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
@Override
public void run() {
for(int i=0;i<3;i++) {
System.out.println("thread["+Thread.currentThread().getName()+"]sn["+sn.getNextNum()+"]");
}
}
}
}
//output
thread[Thread-2]sn[1]
thread[Thread-0]sn[1]
thread[Thread-1]sn[1]
thread[Thread-0]sn[2]
thread[Thread-2]sn[2]
thread[Thread-0]sn[3]
thread[Thread-1]sn[2]
thread[Thread-2]sn[3]
thread[Thread-1]sn[3]
通过输出可见三个线程共享SequenceNumber变量,但没有相互影响自己的计数器
ThreadLocal与Thread同步机制的比较
对于多线程资源共享问题,同步机制采用了"以时间换空间"的方式:访问串行化,对象共享化。而ThreadLocal采用了"以空间换时间"的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响