美文网首页
谈谈你对ThreadLocal的理解

谈谈你对ThreadLocal的理解

作者: 笨比乔治 | 来源:发表于2020-11-12 15:34 被阅读0次

    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

    相关文章

      网友评论

          本文标题:谈谈你对ThreadLocal的理解

          本文链接:https://www.haomeiwen.com/subject/oengmktx.html