ThreadLocal
1. 概述
- 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的
ThreadLocal
类正是为了解决这样的问题。ThreadLocal
类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal
类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。 ThreadLocal
并非是一个线程的本地实现版本,它并不是一个Thread
,而是threadlocalvariable(线程局部变量)
。也许把它命名为ThreadLocalVar
更加合适。线程局部变量(ThreadLocal)
其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
2. 作用
ThreadLocal
是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal
比直接使用synchronized
同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
3. 源码分析
由Thread.java
入手
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
* 属于这个线程的ThreadLocal值,这个映射由ThreadLocal类维护
* */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
* InheritableThreadLocal值属于这个线程。这个映射由InheritableThreadLocal类维护。
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
- 从上述源代码可以看出
Thread
类中有一个threadLocals
和一个inheritableThreadLocals
变量,它们都是ThreadLocalMap
类型的变量,我们可以把ThreadLocalMap
理解为ThreadLocal
类实现的定制化的HashMap
。默认情况下这两个变量都是null
,只有当前线程调用ThreadLocal
类的set
或get
方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap
类对应的get()
、set()
方法。
由ThreadLocal.java
入手
/**
* 返回此线程局部变量的当前线程副本中的值。
* 如果变量对当前线程没有值,它首先被初始化为调用{@link #initialValue}方法返回的值。
*/
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;
}
}
return setInitialValue();
}
/**
* 将此线程局部变量的当前线程副本设置为指定的值。
* 大多数子类不需要覆盖这个方法,只依赖于{@link #initialValue}方法来设置线程局部变量的值。
* @param value 存储在当前线程本地副本中的值。
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* set()的变体来建立initialValue。
* 当用户覆盖了set()方法时,用来代替set()。
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
/**
* 获取与ThreadLocal关联的映射,
* InheritableThreadLocal覆盖。
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 为这个线程局部变量删除当前线程的值。
* 如果这个线程局部变量随后被当前线程{@linkplain #get read}调用,它的值将通过调用其{@link #initialValue}方法重新初始化,除非它的值在此期间被当前线程的{@linkplain #set set}初始化。
* 这可能导致在当前线程中多次调用{@code initialValue}方法。
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
- 通过其中,可得:最终的变量是放在了当前线程的
ThreadLocalMap
中,并不是存在ThreadLocal
上,ThreadLocal
可以认为只是ThreadLocalMap
的封装,传递了变量值。ThrealLocal
类中可以通过Thread.currentThread()
获取到当前线程对象后,直接通过getMap(Thread t)
可以访问到该线程的ThreadLocalMap
对象。 - ThreadLocal内部维护的是一个类似Map的ThreadLocalMap数据结构,key为当前对象的Thread对象,值为Object对象。
/**
* 构造一个初始包含(firstKey, firstValue)的新映射。
* ThreadLocalMaps是惰性构造的,所以我们只在至少有一个条目要放入时才创建一个。
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- 若在同一个线程中声明了两个
ThreadLocal
对象的话,会使用Thread内部都是使用仅有 那个ThreadLocalMap
存放数据的,ThreadLocalMap
的key
就是ThreadLocal
对象,value
就是ThreadLocal
对象调用set
方法设置的值。
4. 总结
- 每个线程Thread有一个
ThreadLocal.ThreadLocalMap
类型的成员变量threadLocals
,这个threadLocals
就是用来存储实际的变量副本的,键值为当前ThreadLocal
变量,value
为变量副本(即T类型的变量)。 初始时,在Thread
里面,threadLocals
为空,当通过ThreadLocal
变量调用get()
方法或者set()
方法,就会对Thread
类中的threadLocals
进行初始化,并且以当前ThreadLocal
变量为键值,以ThreadLocal
要保存的副本变量为value
,存到threadLocals
。 然后在当前线程里面,如果要使用副本变量,就可以通过get
方法在threadLocals
里面查找。
5. 实例
/**
* 本地线程 测试
*
* @author murphy
*/
public class ThreadLocalTest {
// 可以理解为一个容器:特殊点 - 只能存放一个数据
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
private List<String> list = new ArrayList<String>();
class MyThread1 extends Thread {
@Override
public void run() {
threadLocal.set("murphy");
list.add("List1");
System.out.println("MyThread1 - threadLocal - " + threadLocal.get());
System.out.println("MyThread1 - list - " + list.get(0));
}
}
class MyThread2 extends Thread {
@Override
public void run() {
// threadLocal.set("cici");
// list.add("List2");
System.out.println("MyThread2 - threadLocal - " + threadLocal.get());
System.out.println("MyThread2 - list - " + list.get(0));
}
}
/**
* 验证:每一个线程都有一个独立的副本
* 输出:
* MyThread1 - threadLocal - murphy
* MyThread2 - threadLocal - null
* MyThread1 - list - List1
* MyThread2 - list - List1
*/
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
MyThread1 t1 = test.new MyThread1();
MyThread2 t2 = test.new MyThread2();
t1.start();
t2.start();
}
/**
* 输出:Cici
*/
public static void main1(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
// 添加数据
test.threadLocal.set("murphy");
// 再次添加会覆盖先前的值
test.threadLocal.set("Cici");
// 取出数据
String s = test.threadLocal.get();
System.out.println(s);
}
}
6. 实战 - 优化Mybatis工具类
MybatisUtil.java
/**
* 工具类:获取连接 - 关闭连接
*
* @author murphy
*/
public class MybatisUtil {
private static ThreadLocal<SqlSession> sqlSessionThreadLocal = new ThreadLocal<>();
private static SqlSessionFactory factory;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
// 创建工厂
factory = new SqlSessionFactoryBuilder().build(reader);
}
/**
* 获取连接
* @return
*/
public static SqlSession getSqlSession(){
// 优化 - 从ThreadLocal中获取SqlSession
SqlSession sqlSession = sqlSessionThreadLocal.get();
if (sqlSession == null) {
// 创建sqlSession
sqlSession = factory.openSession();
// 将sqlSession与线程进行绑定
sqlSessionThreadLocal.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭连接
*/
public static void closeSqlSession() {
// 优化 - 从ThreadLocal中获取SqlSession
SqlSession sqlSession = sqlSessionThreadLocal.get();
if (sqlSession != null) {
sqlSession.close();
// 移除局部变量的值
sqlSessionThreadLocal.remove();
}
}
}