美文网首页
ThreadLocal学习笔记

ThreadLocal学习笔记

作者: 皮多堡 | 来源:发表于2020-08-04 20:52 被阅读0次

    一、经典实用场景

    • 1.Spring中事务的隔离级别

      public abstract class TransactionSynchronizationManager {
          private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
          private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
          private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
          private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
          private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
          private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
          private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
          }
      
    • 2.状态(回话)信息共享

      比如在项目中标记微信用户的openId,这么做是因为,在之前的老系统中添加微信小程序的功能,为了尽可能最小的影响之前系统的认证体系,又能满足现有小程序功能中的认证和会话标记。

      /**
       * 微信用户信息(openId)上下文
       * @author haopeng
       * @date 2020-06-09 15:19
       */
      public class MaUserContextHolder {
          private static ThreadLocal<String > threadLocal = new ThreadLocal<>();
      
          public static void setOpenId(String  openId){
              threadLocal.set(openId);
          }
      
      
          public static String getOpenId() {
              return threadLocal.get();
          }
      
      
          public static void remove() {
              threadLocal.remove();
          }
      
      }
      
    • 3.解决线程安全问题

      典型应用就是SimpleDateFormat

       //SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),
          // 然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。
          private static ThreadLocal dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
      

    二、示例代码

    分析一下代码执行结果

    
    /**
     * @author haopeng
     * @date 2020-07-31 11:05
     */
    public class TheadLocalTest {
        //1.声明ThreadLocal变量
        private static ThreadLocal<String> localName = ThreadLocal.withInitial(() -> "张三");
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("主线程第一次获取到的值为: " + localName.get());
            localName.set("李四");
            //创建子线程
            Thread thread = new Thread(() -> {
                System.out.println("内部线程获取到的的值为:" + localName.get());
            });
            thread.start();
            thread.join();
            System.out.println("主线程第二次获取到的值为: " + localName.get());
            localName.remove();
    
            //如果需要线程数据共享可以使用InheritableThreadLocal
            ThreadLocal<String> local = new InheritableThreadLocal<>();
            local.set("测试线程数据共享");
        }
    }
    
    运行结果:
        主线程第一次获取到的值为: 张三
        内部线程获取到的的值为:张三
        主线程第二次获取到的值为: 李四
        
    
    • join()是为了保证等待子线程执行完毕
    • 通过运行结果可以发现,变量张三在线程之间是相互隔离的

    三、原理分析

     /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    通过分析以上set方法的源码可以看出是通过当前线程获取一个ThreadLocalMap,并最终将设置的值以键值对形式存储在此Map中。

    继续查看getMap(t)源码

     ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    

    这里可以看到ThreadLocal.ThreadLocalMap是线程类Thread中的一个静态变量,这也就是其数据隔离的本质原因了-----> 每一个线程对象内部维护一个ThreadLocalMap,隔离的数据就是跟线程绑定的,而不同线程之间是不能共享的

    查看ThreadLocalMap关键源码

    static class ThreadLocalMap {
        //1 Entry对象
          static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
        // 2.Entry数组
        private Entry[] table;
        
        //3. set方法
             private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    }
    

    通过源码可以看出:

    • 1.ThreadLocalMap并没有实现Map接口,所以它与HashMap不大一样,并且其内部并没有链表的概念

    • 2.相同的地方是ThreadLocalMap内部也是一个Entry

    • 3.Entry对象实现了WeakReference其内部结构如下:

    • 4.当插入数据时,计算下标int i = key.threadLocalHashCode & (len-1),当该数组对应下标位置没有元素时,直接初始化一个Entry放到这里。当出现Hash冲突时,继续判断key是否相等,也就是判断这个当前的ThreadLocal是否相等,如果相等,进行更新,否则继续找下一个位置,知道找到一个为空的位置

    四、内存泄漏

    1. 首先要明白,由于是线程绑定的实现数据隔离,自然醒到数据时存在占空间中的,其实不完全是,只能说每一个线程的ThreadLocalMap的引用是存在栈空间中的,实际上ThreadLocalMap也是被创建出来的,所以存在堆空间中的

    2. 由于存在堆空间中的,所以需要垃圾回收。Entry中的Key实现了弱引用,这就导致了,当这ThreadLocal对象使用之后,没有外部强引用时,下次gc时会被回收,而此时时间的value值没有被回收,这就是内存泄漏的原因

      static class Entry extends WeakReference<ThreadLocal<?>> {
                  /** The value associated with this ThreadLocal. */
                  Object value;
      
                  Entry(ThreadLocal<?> k, Object v) {
                      super(k);
                      value = v;
                  }
              }
      
      1. 解决内存泄漏的办法就是使用完手动remove

    最后,如果想使用线程之间共享的一个全局变量呢 ,可以使用InheritableThreadLocal他是ThreadLocal的子类

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
        /**
         * Computes the child's initial value for this inheritable thread-local
         * variable as a function of the parent's value at the time the child
         * thread is created.  This method is called from within the parent
         * thread before the child is started.
         * <p>
         * This method merely returns its input argument, and should be overridden
         * if a different behavior is desired.
         *
         * @param parentValue the parent thread's value
         * @return the child thread's initial value
         */
        protected T childValue(T parentValue) {
            return parentValue;
        }
    
        /**
         * Get the map associated with a ThreadLocal.
         *
         * @param t the current thread
         */
        ThreadLocalMap getMap(Thread t) {
           return t.inheritableThreadLocals;
        }
    
        /**
         * Create the map associated with a ThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the table.
         */
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    

    使用如下:

    ThreadLocal<String> local = new InheritableThreadLocal<>();
    local.set("测试线程数据共享");
    

    相关文章

      网友评论

          本文标题:ThreadLocal学习笔记

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