当访问共享的可变数据时,为了线程安全同常需要进行同步,一般避免使用同步的方式就是不共享数据。如果共享的变量只能在单个线程内被访问,那么就不需要进行同步了。这种技术称为线程封闭。
ThreadLocal提供了一种线程封闭的实现。ThreadLocal提供了get和set方法,每个线程调用get方法时只会返回该线程上次调用set方法设置的值(默认没用set值时返回null)。这样就可以避免共享变量在不同的线程之间共享。如下示例:
public class ThreadLocalCase {
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<>();
private static Integer integer = 0;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
LOCAL.set(100);
integer=100;
System.out.println(Thread.currentThread().getName()+" local value:"+LOCAL.get());
System.out.println(Thread.currentThread().getName()+" int value:"+integer);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" local value:"+LOCAL.get());
System.out.println(Thread.currentThread().getName()+" int value:"+integer);
}
}).start();
}
}
//控制台输出
Thread-0 local value:100
Thread-0 int value:100
Thread-1 local value:null
Thread-1 int value:100
这个例子中ThreadLocalCase类持有一个ThreadLocal类型的类变量和一个Integer类型的类变量。当线程0和对integer操作时,线程0设置的值在线程1中可见,显然integer对象不是线程安全的。而同样线程0对于ThreadLocal对象中的integer进行操作时,线程0设置的值在线程1中不可见。说明实现了线程隔离。
ThreadLocal是如何实现线程封闭的呢?
对于每一个运行的线程都会有一个对应的线程类Thread实例,在代码中我们可以通过Thread.currentThread()获取这个线程对象。Thread类中持有一个ThreadLocal.ThreadLocalMap类型的属性,这个属性的访问权限是package级别的,java.lang包以外的类无法操作这个变量。而ThreadLocal类和Thread类在同一个包下,可以通过Thread实例操作这个属性。
public
class Thread implements Runnable {
......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
.....
}
当调用ThreadLocal的set方法时,会先获取当前线程实例,然后拿到Thread实例的ThreadLocal.ThreadLocalMap类型的引用,将当前ThreadLocal对象引用作为key,将要保存的值作为value保存到map中。这样就完成赋值操作。
public void set(T value) {
//获取当前线程实例
Thread t = Thread.currentThread();
//获取当前线程实例中的ThreadLocal.ThreadLocalMap类型的引用
ThreadLocalMap map = getMap(t);
if (map != null)
//将当前ThreadLocal对象引用作为key,将要保存的值作为value保存到map中
map.set(this, value);
else
//第一次调用时map为空,进行初始化
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
当调用ThreadLocal的get方法时,同样的,会先获取当前线程实例,然后拿到Thread实例的ThreadLocal.ThreadLocalMap类型的引用,将当前ThreadLocal对象引用作为key,从map中获取对应的值。
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;
}
}
//若map为空,返回null
return setInitialValue();
}
上面例子中,我们在Thread-0线程中,set一个值,这个值最终保存在Thread-0线程所对应的线程实例的threadLocalMap中。所以在Thread-1线程调用get方法时,Thread-1线程所对应的线程实例的threadLocalMap仍然为空,所以会返回null。