面试专题:谈谈你对ThreadLocal的理解

ThreadLocal 解决了什么问题?内部源码是怎么样的?
作用:为每个线程创建一个副本,实现在线上的上下文传递对象

例子1:证明 ThreadLocal 为每个线程创建一个副本

public class ThreadLocalTest {
    
    
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
    
    

        Runnable runnable = getRunnable();

        //开启多线程执行任务
        new Thread(runnable).start();
        //避免线程执行太快导致时间戳相同
        Thread.sleep(10);
        new Thread(runnable).start();
    }

    private static Runnable getRunnable() {
    
    
        return () -> {
    
    
            Long result = threadLocal.get();
            if (result == null) {
    
    
                threadLocal.set(System.currentTimeMillis());
            }
            System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
        };
    }
}

输出的结果是不同的

Thread-0—>1614918217114
Thread-1—>1614918217116

为什么可以给每个线程保存一个不同的副本?
那我们来分析源码

public T get() {
    
    
		//	1. 获取当前线程
        Thread t = Thread.currentThread();
        //  2. 获取到当前线程对应的Map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
        	// 3. this= ThreadLocal,以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方法的源码分析,才可以更好的理解

	public void set(T value) {
    
    
		//	1. 获取当前线程
        Thread t = Thread.currentThread();
         //  2. 获取到当前线程对应的Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	// 3.往map里面存储一个键值对
            map.set(this, value);
        else
            createMap(t, value);
    }

通过set方法可以看到和get一样,都是先获取当前线程对应的map。
所以,我们得到结论:
每个线程都会有对应的ThreadLocalMap,ThreadLocalMap用来保存键值对。
在这里插入图片描述

那么ThreadLocal 在实际开发中解决了什么问题?

hibernate管理session,mybatis 管理 sqlsession、管理connection
前提知识:不管是什么框架,最本质的操作都是基于JDBC,当我们需要跟数据库打交道时,都需要有一个 connection 。

public static void main(String[] args) {
    
    
        UserService userService=new UserService();
        userService.saveUser();
    }

    public static class Connection {
    
    
        Object execute(String sql){
    
    
            return null;
        }
    }
    public static class UserService {
    
    
        void saveUser(){
    
    
            UserDao user=new UserDao();
            user.add();
            RoleDao role=new RoleDao();
            role.update();
        }
    }

    public static class UserDao {
    
    
        void add(){
    
    
            Connection connection=new Connection();
            connection.execute("update User set name=1");
        }
    }
    public static class RoleDao {
    
    
        void update(){
    
    
            Connection connection=new Connection();
            connection.execute("update Role set role=1");
        }
    }

在这里插入图片描述

如果代码被上面的方式来管理 connection,我们还可以保证service 的事务控制吗?
这是不行的,假设第一个dao 操作成功了,那么它就提交事务了,而第二个 dao 操作失败了,它回滚了事务,但不会影响到第一个 dao 的事务,因为上面这么写是两个独立的事务。

那么怎么解决,上面的根源就是两个 dao 是不同的 connection ,所以我们保证同一个 Connection 就可以了。ThreadLocal就可以应用到当前的场景了,我们一起看下例子

public static void main(String[] args) {
    
    
        UserService userService = new UserService();
        userService.saveUser();
    }

    public static class ConnectionUtils {
    
    
        private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

        static Connection get() {
    
    
            if (threadLocal.get() == null) {
    
    
                threadLocal.set(new Connection());
            }
            return threadLocal.get();
        }
    }

    public static class Connection {
    
    
        Object execute(String sql) {
    
    
            return null;
        }
    }

    public static class UserService {
    
    
        void saveUser() {
    
    
            UserDao user = new UserDao();
            user.add();
            RoleDao role = new RoleDao();
            role.update();
        }
    }

    public static class UserDao {
    
    
        void add() {
    
    
            Connection connection = ConnectionUtils.get();
            connection.execute("update User set name=1");
            System.out.println("UserDao...................." + connection.hashCode());
        }
    }

    public static class RoleDao {
    
    
        void update() {
    
    
            Connection connection = ConnectionUtils.get();
            connection.execute("update Role set role=1");
            System.out.println("RoleDao...................." + connection.hashCode());
        }
    }

Dao层输出的connection如下

UserDao…2094777811
RoleDao…2094777811

猜你喜欢

转载自blog.csdn.net/lxn1023143182/article/details/114390215