ThreadLocal的含义及使用
ThreadLocal看字面意思,Thread 线程,Local 本地,它是什么意思?通过一个小程序来解释:
public class ThreadLocalTest_01 {
volatile static Person person = new Person();
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出name时,lisi已经被线程t2改为zhangsan
System.out.println(person.name);
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在t1线程没有输出name前,把name改成lisi
person.name = "lisi";
}, "t2").start();
}
}
class Person {
String name = "zhangsan";
}
创建一个Person类,属性name初始化为zhangsan,创建person对象。
起两个线程t1、t2。t1睡2s后打印person.name,t2睡1s后把person.name改成lisi。因为t1在打印前,name被先睡醒的t2改成了lisi,所以最后打印lisi。
上面这个程序说明创建一个对象后,它在多个线程之间是共享的。
那么如何才能做到这个person对象在每个线程中都自己独有的一份呢?
用ThreadLocal就能做到,可以在ThreadLocal中设置值,这个值是各自线程独有的,别的线程访问不到。
下面看小程序里怎么使用:
public class ThreadLocalTest_02 {
// volatile static Person person = new Person();
static ThreadLocal<Person> personThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程t2在自己的threadLocal里初始化person对象后,在该线程中get为null
System.out.println(personThreadLocal.get());
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在当前线程t2的threadLocal里添加一个person对象
personThreadLocal.set(new Person());
}, "t2").start();
}
}
class Person {
String name = "zhangsan";
}
这个程序的输出结果为null。
线程t2在自己threadLocal里添加了一个person对象,但是线程t1的threadLocal是拿不到这个对象的。
ThreadLocal源码阅读
明明是同一个ThreadLocal对象,那为什么一个线程设置值了之后,其它线程就读取不到呢?
这需要看它的源码实现,先来看一下它的set方法:
// 参数类型是创建ThreadLocal对象时使用泛型规定的
public void set(T value) {
// 获取当前线程t
Thread t = Thread.currentThread();
// 返回线程t的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// key是当前线程的threadLocal,value是要设置的值
map.set(this, value);
} else {
// 没有的话就new一个ThreadLocalMap把值设置进去
createMap(t, value);
}
}
里面用到了一个map容器ThreadLocalMap,它的key是threadLocal,value就是专属这个线程的值。
这个map到底是什么东西,再看一下getMap的源码:
// ThreadLocal类的方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap(t)方法返回了这个线程的threadLocals变量。
点击t.threadLocals,跳到了Thread类:
// Thread类部分源码
public class Thread implements Runnable {
// 省略其它...
ThreadLocal.ThreadLocalMap threadLocals = null;
// 省略其它...
}
可以看到每个线程都有自己的一个ThreadLocalMap。
这时就明白了 map.set(this, value)就是设置当前线程的map(threadLocals),相当于:Thread.currentThread().map(ThreadLocal,value) (伪代码)
来梳理一下,上一部分小程序中线程t2执行personThreadLocal.set(new Person())时,是把person对象set到了自己的map里,t1线程去访问的也是自己的属于t1线程的map,所以是读不到值的,因此使用ThreadLocal的时候,用set和get就完全的把他隔离开了,就是自己线程里面所特有的,其它的线程是没有的。
为什么要用ThreadLocal?
根据Spirng的声明式事务来解析,为什么要用ThreadLocal。
声明式事务一般是要通过数据库的,但是我们知道Spring结合Mybatis,是可以把整个事务写在配置文件中的,而这个配置文件里的事务,实际上是管理了一系列的方法,方法1、方法2、方法3....,
这些方法里面可能写了:比如第1个方法写了去配置文件里拿到数据库连接Connection,第2个、第3个都是一样去拿数据库连接。然后声明式事务可以把这几个方法合在一起,视为一个完整的事务。
如果说在这些方法里,每一个方法拿的连接,不是同一个对象,事务是不可能成功的。Connection会放到一个连接池里边,如果第1个方法拿的是第1个Connection,第2个拿的是第2个,第3个拿的是第3个,不同的Connection是不能形成一个完整的事务的。
那怎么保证是同一个Connection呢?
把这Connection放到这个线程的本地对象ThreadLocal里面,以后再拿的时候,实际是从ThreadLocal里拿的,第1个方法拿的时候就把Connection放到ThreadLocal里面,后面的方法要拿的时候,从ThreadLocal里直接拿,不从线程池拿。
参考:马士兵《多线程与高并发》