谈谈对ThreadLocal的理解
ThreadLocal是Java中提供的线程本地存储机制,叫做线程变量,主要是做数据隔离,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
@Slf4j
public class Ctest {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
// 线程1,设置变量值
new Thread(()->{
// 睡眠1S
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 往tl里面设置变量
tl.set(new Person("zhangsan"));
// 打印输出
log.info(String.valueOf(tl.get()));
}).start();
// 线程2,不设置变量值,去读1中设置的值
new Thread(()->{
// 睡眠2s
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取tl
log.info(String.valueOf(tl.get()));
}).start();
}
static class Person {
String name = "zhangsan";
public Person(String name) {
this.name = name;
}
}
}
打印输出:
21:28:54.292 [Thread-0] INFO com.example.stream.test.Ctest - com.example.stream.test.Ctest$Person@550c0234
21:28:55.284 [Thread-1] INFO com.example.stream.test.Ctest - null
同一个容器,线程1获取到了值,线程2获取为null,这是为什么呢?其实这就是ThreadLocal线程局部变量的真正含义,就是ThreadLocal会为每个线程创建副本变量,那么线程只能读取到自己内部的副本变量,因为线程2没有设置变量,所以没办法获取到,它是线程私有容器。
当我们往各自线程中设置变量值的时候,是否真的能够获取到呢?
@Slf4j
public class Ctest {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
// 线程1,设置变量值
new Thread(()->{
// 睡眠1S
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 往tl里面设置变量
tl.set(new Person("zhangsan"));
// 打印输出
log.info(String.valueOf(tl.get()));
}).start();
// 线程2,设置变量值
new Thread(()->{
// 睡眠2s
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 往tl里面设置变量
tl.set(new Person("zhangsan"));
// 打印输出
log.info(String.valueOf(tl.get()));
}).start();
}
static class Person {
String name = "zhangsan";
public Person(String name) {
this.name = name;
}
}
}
打印输出:
21:42:45.001 [Thread-0] INFO com.example.stream.test.Ctest - com.example.stream.test.Ctest$Person@550c0234
21:42:45.997 [Thread-1] INFO com.example.stream.test.Ctest - com.example.stream.test.Ctest$Person@57580186
这个时候就获取到了。并且我们两个线程设置的值一样,获取到的各自的对象hascode值不一样
ThreadLocal原理
当我们调用set()方法的时候,源码如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程对象,获取当前线程ThreadLocalMap对象,判断是否存在,不存在则创建一个Entry对象,存储则更新当前ThreadLocal的值,ThreadLocalMap中,是一个Entry[]数组,每个entry是一个map对象,key是当前ThreadLocal,value是Object值。
ThreadLocal在线程池中会造成内存溢出,解决办法是,在用完ThreadLocal之后,手动调用remove()方法,清除Entry对象。
ThreadLocal使用场景
ThreadLocal使用场景: 用来解决数据库连接、cookie,Session管理,Spring事务等,还有就是我们的日期格式化工具SimpleDateFormat,使用ThreadLocal解决它线程不安全的问题,其实就是做数据隔离。
Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
Spring的事务场景: 通过@Transactional开启自动支持事务处理,假设在这样的业务操作过程中,添加员工的同时将其设置为操作员,分配其角色,在使用方法调用的时候形成方法依次向下调用,也就是事务的传播机制,其中的事务处理,他的底层就有ThreadLocal的使用。。
@Override
@Transactional(rollbackFor = Exception.class)
public void add(Employee employee) {
// 添加员工
empMapper.add(employee);
// 调用操作员添加接口
userService.add(employee);
// 调用角色添加接口
roleService.add(employee);
}
这个时候添加员工的方法就会自动支持事务的处理,此时userService和roleService都是通过@Autowired注入的,添加操作员和添加角色也会自动的支持事务处理,开启事务的时候,在使用方法调用的时候形成方法依次向下调用,他的底层就有ThreadLocal的使用。