ThreadLocal

ThreadLocal是线程本地存储,为每个线程提供变量副本,避免线程安全问题。它在多线程环境中,如数据库连接和Session管理中广泛应用。ThreadLocal的工作原理包括:每个Thread有一个ThreadLocalMap,存储ThreadLocal对象及其对应的变量副本。get()、set()和remove()方法分别用于获取、设置和移除副本。未设置时,get()返回null,除非重写initialValue()方法提供默认值。
摘要由CSDN通过智能技术生成

简介

  • 线程本地变量,或线程本地存储,可以为变量在每个线程中都创建一个副本,使每个线程都可以访问自己内部的副本变量。

  • 举个例子:

      class ConnectionManager {
    
          private static Connection connect = null;
           
          public static Connection openConnection() {
              if(connect == null){
                  connect = DriverManager.getConnection();
              }
              return connect;
          }
           
          public static void closeConnection() {
              if(connect!=null)
                  connect.close();
          }
      }
    
  • 有这样一个数据库连接管理类,它在单线程中使用没有任何问题,但是如果在多线程中使用呢?

  • 显然,在多线程中使用会存在线程安全问题。

  • 第一,两个方法都没有进行同步,很可能在openConnection()中会多次创建connect。

  • 第二,由于connect是共享变量,那么在调用connect的地方必然需要使用同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作时,另一个线程会调用closeConnection()关闭连接。

  • 故,必须将两个方法进行同步处理,并且在调用connect的地方也进行同步。

  • 而这样将会大大影响程序执行效率,因为一个线程在使用connect时,其他线程只有等待。

  • 那可不可以干脆不要共享connect变量,让每个线程都单独拥有一个connect,各个线程对connect的访问实际是没有依赖关系的?

      class ConnectionManager {
    
          private  Connection connect = null;
           
          public Connection openConnection() {
              if(connect == null){
                  connect = DriverManager.getConnection();
              }
              return connect;
          }
           
          public void closeConnection() {
              if(connect!=null)
                  connect.close();
          }
      }
       
       
      class Dao{
          public void insert() {
              ConnectionManager connectionManager = new ConnectionManager();
              Connection connection = connectionManager.openConnection();
               
              //使用connection进行操作
               
              connectionManager.closeConnection();
          }
      }
    
  • 由于每次都是在方法内部创建连接,线程之间不存在线程安全带的问题,但由于需要在方法中频繁地开关数据库连接,将会导致服务器压力非常大,并且严重影响程序执行性能。

  • 这种情况下ThreadLocal就派上用场了,它可以在每个线程中都对该变量创建一个副本,即每个线程内都会有一个该变量,且线程之间互不影响,但由于在每个线程中都创建了副本,这也消耗了资源,需考虑内存的占用会不会比不使用ThreadLocal还要大。

源码

  • 原理:每个Thread内部有一个副本集合ThreadLocalMap对象threadLocals,其键为当前ThreadLocal对象,值为变量副本。初始调用get()/set()时,就会对Thread类中的threadLocals集合初始化;然后如果要使用副本变量,就可以通过get()在threadLocals里查找。

  • public T get():获取当前线程中保存的副本。

  • public void set(T value):设置当前线程中某个变量的副本。

  • public void remove():移除副本;调用set()之前要先移除,保证副本集合内只有一个键值对。

  • protected T initialValue():一般是用来在使用时进行重写的,是一个延迟加载的方法。

  • 首先看get():

      public T get() {
          //获得当前线程
          Thread t = Thread.currentThread();
          //以该线程为键,获取其值,即为之前保存的变量副本
          ThreadLocalMap map = getMap(t);
          
          if (map != null) {
              //若集合不为空,则根据键获得对应的值
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null) {
                  T result = (T)e.value;
                  return result;
              }
          }
          
          //若集合为空,则创建一个集合
          return setInitialValue();
      }
    
  • getMap():

      ThreadLocalMap getMap(Thread t) {
          //返回副本集合
          return t.threadLocals;
      }
    
  • ThreadLocalMap类:副本集合,一个线程可有多个ThreadLocal对象,每个ThreadLocal对象对应一个变量副本。

      static class ThreadLocalMap {
    
          static class Entry extends WeakReference<ThreadLocal<?>> {
              Object value;
    
              //使用ThreadLocal作为键,value为值
              Entry(ThreadLocal<?> k, Object v) {
                  super(k);
                  value = v;
              }
          }
      }
    
  • setInitialValue():

      private T setInitialValue() {
          //未重写情况下返回null
          T value = initialValue();
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          
          //如果map不为空,就设置键值对
          if (map != null)
              map.set(this, value);
              
          //为空,则创建副本集合ThreadLocalMap
          else
              createMap(t, value);
          return value;
      }
    
  • 因此,get()之前必须进行过set(),否则返回的value将为null,除非重写initialValue()返回一个默认值作为value。

  • createMap():

      void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
      }
    
  • 测试:

      public class Test {
          ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
          ThreadLocal<String> stringLocal = new ThreadLocal<String>();
       
          public void set() {
              longLocal.set(Thread.currentThread().getId());
              stringLocal.set(Thread.currentThread().getName());
          }
           
          public long getLong() {
              return longLocal.get();
          }
          public String getString() {
              return stringLocal.get();
          }
          
          public static void main(String[] args) throws InterruptedException {
              final Test test = new Test();
              
              test.set();
              System.out.println(test.getLong()); //1
              System.out.println(test.getString()); //main
           
              //开新线程
              Thread thread1 = new Thread(){
                  public void run() {
                      test.set();
                      System.out.println(test.getLong()); //8
                      System.out.println(test.getString()); //Thread-0
                  };
              };
              thread1.start();
              thread1.join();
               
              System.out.println(test.getLong()); //1
              System.out.println(test.getString()); //main
          }
      }
    
  • 测试结果:

应用

  • 最常用于解决数据库连接、Session管理等。

      private static ThreadLocal<Connection> connectionHolder
          = new ThreadLocal<Connection>() {
              public Connection initialValue() {
                  return DriverManager.getConnection(DB_URL);
              }
          };
       
      public static Connection getConnection() {
          return connectionHolder.get();
      }
    
    private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值