• ThreadLocal详解



    一、ThreadLocal简介

    • ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
      • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
      • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

    二、手写ThreadLocal模拟应用

    1.MyThreadLocal

    package com.bdg.threadlocal;
    
    import java.util.HashMap;
    import java.util.Map;
    /**
     * 自定义一个ThreadLocal类
     */
    public class MyThreadLocal<T> {
        /**
         * 所有需要和当前线程绑定的数据都要放到这个容器中
         */
        private final Map<Thread, T> map = new HashMap<>();
    
        /**
         * 向ThreadLocal中绑定元素
         * @param obj
         */
        public void set(T obj) {
            map.put(Thread.currentThread(), obj);
        }
    
        /**
         * 从ThreadLocal中取元素
         * @return
         */
        public T get() {
            return map.get(Thread.currentThread());
        }
    
        /**
         * 移除ThreadLocal中的元素
         */
        public void remove() {
            map.remove(Thread.currentThread());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.模拟连接对象Connection并编写工具类

    (1)Connection

    package com.bdg.threadlocal;
    
    public class Connection {}
    
    • 1
    • 2
    • 3

    (2)DBUtil

    package com.bdg.threadlocal;
    
    public class DBUtil {
        //静态变量类加载时执行,并且只执行一次
        //全局的大map
        /*所有需要与线程绑定的数据的大容器*/
        private static final MyThreadLocal<Connection> local = new MyThreadLocal<>();
    
        /**
         * 每一次都调用这个方法来获取Connection对象
         */
        public static Connection getConnection() {
            Connection conn = local.get();
            if (conn == null) {
                //第一次调用getConnection方法时conn一定是null.
                //空的就需要new
                conn = new Connection();
                //将new出来的conn对象绑定到大map中
                local.set(conn);
            }
            return conn;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.编写程序使用自己编写的MyThreadLocal

    (1)UserDao

    package com.bdg.threadlocal;
    
    public class UserDao {
        public void insert() {
            Thread thread = Thread.currentThread();
            System.out.println(thread);
    
            Connection conn = DBUtil.getConnection();
            System.out.println(conn);
    
            System.out.println("User DAO insert");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2)UserService

    package com.bdg.threadlocal;
    
    public class UserService {
    
        private UserDao userDao = new UserDao();
        public void save() {
            Thread thread = Thread.currentThread();
            System.out.println(thread);
    
            Connection conn = DBUtil.getConnection();
            System.out.println(conn);
    
            userDao.insert();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)Test

    package com.bdg.threadlocal;
    
    public class Test {
        public static void main(String[] args) {
    
            Thread thread = Thread.currentThread();
            System.out.println(thread);
    
            //调用service
            UserService userService = new UserService();
            userService.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.结果展示

    在这里插入图片描述


    三、ThreadLocal的应用场景

    • ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
      • 1.存储用户 Session
      • 2.数据库连接,处理数据库事务
      public class DBUtil {
      
          private static ResourceBundle bundle = ResourceBundle.getBundle("resources/jdbc");
          private static String driver = bundle.getString("driver");
          private static String url = bundle.getString("url");
          private static String user = bundle.getString("user");
          private static String password = bundle.getString("password");
          private static ThreadLocal<Connection> local = new ThreadLocal<>();
          //不然创建对象,因为工具类中的方法都是静态的,不需要创建对象。
          //为了防止创建对象,故将构造方法私有化
          private DBUtil() {}
      
          //DBUtil类加载时注册驱动
          static {
              try {
                  Class.forName(driver);
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
          }
      
          //这里没有使用数据库连接池,直接创建连接对象
          public static Connection getConnection() throws SQLException {
              Connection conn = local.get();
              if (conn == null) {
                  conn = DriverManager.getConnection(url, user, password);
                  local.set(conn);
              }
              System.out.println(conn);
              return conn;
          }
      
          public static void close (Connection conn, Statement stmt, ResultSet rs) {
              if (conn != null) {
                  try {
                      conn.close();
                      //Tomcat是支持多线程的,假设张三使用了T1连接对象,其他的用户也可能使用T1连接对象
                      local.remove();
                  } catch (SQLException e) {
                      throw new RuntimeException(e);
                  }
              }
              if (stmt != null) {
                  try {
                      stmt.close();
                  } catch (SQLException e) {
                      throw new RuntimeException(e);
                  }
              }
              if (rs != null) {
                  try {
                      rs.close();
                  } catch (SQLException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 3.数据跨层传递(controller、service、dao)
        • 上面手写 ThreadLocal 示例中处理了跨层访问的问题。
      • 4.Spring 使用 ThreadLocal 解决线程安全问题

    三、总结

    • 通过上面的模拟我们可以看出,ThreadLocal 其实就是一个全局的大 Map,ThreadLocal 的 key 部分存储的是线程对象,我们可以将所有需要和当前线程绑定的数据都要放到这个容器中。
    • 在 JDK 中给我们内置了 ThreadLocal ,不需要我们自己手动来写了,可以直接使用。

    1.如何正确的使用ThreadLocal

    • ① 将 ThreadLocal 变量定义成 private static 的,这样的话 ThreadLocal 的生命周期就更长,由于一直存在 ThreadLocal 的强引用,所以 ThreadLocal 也就不会被回收,也就能保证任何时候都能根据 ThreadLocal 的弱引用访问到 Entry 的 value 值,然后 remove 它,防止内存泄露
    • ② 每次使用完 ThreadLocal,都调用它的 remove() 方法,清除数据。

  • 相关阅读:
    小程序的宿主环境、组件、API、协同工作和发布
    Verilog开源项目——百兆以太网交换机(二)AES加解密模块设计
    C# 集合(一) —— Array类
    ETH POS 2.0 Staking 测试网质押流程
    Time Series Data Augmentation for Deep Learning: A Survey
    Linux互斥体的驱动程序测试
    pyopengl 立方体 正投影,透视投影
    2023-09-12 LeetCode每日一题(课程表 IV)
    springboot日志切面记录请求日志
    【计算机网络】互连网的路由选择协议概述
  • 原文地址:https://blog.csdn.net/weixin_65032328/article/details/136168766