ThreadLocal详解
在多线程应用程序中如果我们想让每个线程有一个自己对应的变量可以进行存取,不受其它线程的影响就可以使用ThreadLocal,而synchronized是通过加锁来保证一次只有一个线程能对共享变量进行修改。
来看一下ThreadLocal的使用:
public class ThreadLocalDemo {
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public void tesetLocal(){
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(2);
System.out.println(threadLocal.get());
threadLocal.remove();
}
}).start();
}
}
}
我们创建了五个线程,每个线程都在操作同一个ThreadLocal的对象threadLocal。
threadLocal.set(2) 向当前线程存2
threadLocal.get() 从当前线程取值取出来的是2
threadLocal.remove() 删除当前线程中的数据
程序是如何能够保证每个线程在存取的时候只是操作自己的变量的呢?来看一下它的结构:
在ThreadLocal当中有一个内部类ThreadLocalMap,它里面有一个键值对的数组,每个线程中有一个变量threadLocals指向一个ThreadLocalMap对象。
ThreadLocal本身并不会存储数据,它相当于一个工具只是提供了get和set方法来操作数据,数据是存储在ThreadLocalMap的键值对数组中的。
来看一下ThreadLocal类的get和set方法:
public class ThreadLocal<T> {
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程指向的ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null) {
//如果不为空则根据当前线程的ThreadLocal对象获取键值对
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//将键对应的值返回
return result;
}
}
//如果为空则创建ThreadLocalMap并初始化
return setInitialValue();
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程指向的ThreadLocalMap对象
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
//如果不为空则添加键值对
map.set(this, value);
else
//否则创建ThreadLocalMap对象并添加键值对
createMap(t, value);
}
}
我们把ThreadLocal类的get和set方法抽取出来研究一下,发现无论是get还是set方法,都是首先获取当前的线程,然后操作当前线程指向的ThreadLocalMap对象
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocal.ThreadLocalMap map = getMap(t); //获取当前线程指向的ThreadLocalMap
每个线程都会创建一个自己的ThreaLocalMap对象,对于每个线程该对象都是不一样的:
createMap(t, value); //创建ThreaLocalMap对象
正因为如此,每个线程可以操作自己的数据而不影响到其它线程。
我们来深入研究一下这行代码:
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
可以看出ThreadLocalMap是ThreadLocal的内部类,Entry又是ThreadLocalMap的内部类,Entry是一个什么结构呢?我们猜测它是一个键值对,看代码:
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建一个Entry数组table
table = new Entry[INITIAL_CAPACITY];
//根据ThreadLocal对象的哈希码和数组长度获取该键值对存储在数组的位置。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建Entry键值对并存放在Entry数组的指定位置
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
从代码发现,我们猜的没错,Entry就是一个键值对,他的键是ThreadLocal对象。
从ThreadLocalMap的构造函数可以看出在创建ThreadLocalMap的时候会去创建一个Entry对象的数组:
table = new Entry[INITIAL_CAPACITY]; //创建一个Entry对象的数组
根据ThreadLocal对象的哈希码和数组的长度可以计算出键值对在该数组中存储的位置:
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
然后将键值对存入数组:
table[i] = new Entry(firstKey, firstValue);
看起来好乱啊,总结一下吧:
1. ThreadLocal相当于一个工具提供了操作数据的get和set方法,它本身并不存储数据。
2. 每个线程对应一个ThreadLocalMap对象,ThreadLocalMap对象中有一个键值对数组,数据就是存在这个键值对数组里面的。
3. 每个线程都有一个自己的ThreadLocalMap对象,所以能够保证存取数据的时候不会影响到其它线程。
4. 当调用ThreadLocal.set(),方法存放数据的时候是将一个键值对存放到ThreadLocalMap的键值对数组中,键值对使用ThreadLocal对象做为键。
ThreadLocal的应用场合:
一个典型的场合就是session,每个请求使用一个线程,每个线程可以操作自己的session互不干扰。
以下是伪代码:
public class SessionUtil {
private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
public static Session getSession() {
//获取当前线程下的session
Session session = threadLocal.get();
if(session==null) {
//如果session为空则获取session放入threadLocal
session = factory.openSession();
threadLocal.set(session);
}
return session;
}
}
public class Test {
Session session = SessionUtil.getSession();
public void tesetLocal(){
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
。。。。
}
}).start();
}
}
}