ThreadLocal解决了什么问题?内部源码是怎么样的?
作用:
为每个线程创建一个副本
实现在线程的上下文传递同一个对象,比如connection
第一个问题:证明ThreadLocal为每个线程创建一个变量副本
public class ThreadLocalTest {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
//开启多个线程来执行任务
Task task = new Task();
new Thread(task).start();
Thread.sleep(10);
new Thread(task).start();
}
static class Task implements Runnable{
@Override
public void run() {
Long result = threadLocal.get();
if(result == null){
threadLocal.set(System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+"->"+threadLocal.get());
}
}
}
}
输出的结果是不同的
问题二:为什么可以给每个线程保存一个不同的副本
那我们来分析源码
Long result = threadLocal.get();
public T get() {
//1.获取当前线程
Thread t = Thread.currentThread();
//2,获取到当前线程对应的map
ThreadLocalMap map = getMap(t);
if (map != null) {
//3.以threadLocal为key,获取到entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//4.获取对应entry的value,就是我们存放到里面的变量的副本
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
我们需要结合set方法的源码分析,才可以更好理解
threadLocal.set(System.currentTimeMillis());
public void set(T value) {
//1.获取到当前线程
Thread t = Thread.currentThread();
//2.获取当前线程对应的map
ThreadLocalMap map = getMap(t);
if (map != null)
//3.往map存放一个键值对
//this ThreadLocal
//value 保存的副本
map.set(this, value);
else
createMap(t, value);
}
所以,我们得到结论:
每个线程都会有对应的map,map来保存键值对。
问题三:ThreadLocal这种特性,在实际开发中解决了什么问题?
比如:hibernate管理session,mybatis管理sqlsession,其内部都是采用ThreadLocal来实现的。
前提知识:不管是什么框架,最本质的操作都是基于JDBC,当我们需要跟数据库打交道的时候,都需要有一个connection。
那么,当我们需要在业务层实现事务控制时,该如何达到这个效果?
我们构建下代码如下:
public class UserService {
//省略接口的声明
private UserDao userDao = new UserDao();
private LogDao logDao = new LogDao();
//事务的边界放在业务层
//JDBC的封装,connection
public void add(){
userDao.add();
logDao.add();
}
}
public class UserDao {
public void add(){
System.out.println("UserDao add。。。");
//创建connection对象
//connection.commit();
//connection.rollback();
}
}
public class LogDao {
public void add(){
System.out.println("LogDao add。。。");
//创建connection对象
//connection.commit();
//connection.rollback();
}
}
如果代码按上面的方式来管理connection,我们还可以保证service的事务控制吗?
这是不行的,假设第一个dao操作成功了,那么它就提交事务了,而第二个dao操作失败了,它回滚了事务,但不会影响到第一个dao的事务,因为上面这么写是两个独立的事务
那么怎么解决。
上面的根源就是两个dao操作的是不同的connection
所以,我们保证是同个connection即可
//事务的边界放在业务层
//JDBC的封装,connection
public void add(){
Connection connection = new Connection();
userDao.add(connection);
logDao.add(connection);
}
上面的方式代码不够优雅
public class ConnectionUtils {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public static Connection getConnection(){
Connection connection = threadLocal.get();
if(connection == null){
connection = new Connection();
threadLocal.set(connection);
}
return connection;
}
}
public class UserDao {
public void add(){
System.out.println("UserDao add。。。");
//创建connection对象
//connection.commit();
//connection.rollback();
Connection connection = ConnectionUtils.getConnection();
System.out.println("UserDao->"+connection);
}
}
到此,我们可以保证两个dao操作的是同一个connection
网友评论