zoukankan      html  css  js  c++  java
  • ThreadLocal源码解析以及初步使用

    ThradLocal

    主要为了实现线程之间的数据隔离的。当线程之间需要维护同样一个属性的时候,但是当前属性不是一个线程安全的属性的时候就需要创建一个ThreadLocal来实现这个属性的原子性。典型的就是dfs属性。

    1.ThreadLocal的set原理

    先走ThreadLocal的set方法

     1  /**
     2      * Sets the current thread's copy of this thread-local variable
     3      * to the specified value.  Most subclasses will have no need to
     4      * override this method, relying solely on the {@link #initialValue}
     5      * method to set the values of thread-locals.
     6      *
     7      * @param value the value to be stored in the current thread's copy of
     8      *        this thread-local.
     9      */
    10     public void set(T value) {
    11         Thread t = Thread.currentThread();
    12         ThreadLocalMap map = getMap(t);
    13         if (map != null)
    14             map.set(this, value);//将当前ThreadLocal实例对象和value传入
    15         else
    16             createMap(t, value);
    17     }

    首先得到线程,然后调用getMap方法得到一个Thread LocalMap,看一下getMap()

     1 /**
     2      * Get the map associated with a ThreadLocal. Overridden in
     3      * InheritableThreadLocal.
     4      *
     5      * @param  t the current thread
     6      * @return the map
     7      */
     8     ThreadLocalMap getMap(Thread t) {
     9         return t.threadLocals;//线程对象的threadLocals属性
    10     }
    1  /* ThreadLocal values pertaining to this thread. This map is maintained
    2      * by the ThreadLocal class. */
    3     ThreadLocal.ThreadLocalMap threadLocals = null;//线程私有的
    4 
    5     /*
    6      * InheritableThreadLocal values pertaining to this thread. This map is
    7      * maintained by the InheritableThreadLocal class.
    8      */
    9     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//用来线程之间传递的

     既然定义为Map自然而然的联想到了HashMap,然后联想到了hash函数 + Entry Table + 链表

    但是根据源码来看,他和HashMap并不相同。他的map只是使用的Entry Table来存储元素,通过hash来定位。

    实际上一个线程可以有多个不同类型的ThreadLocal变量,但是他们都存储在当前线程的ThreadlocalMap里面,所以经过hash之后自然而然就有可能发生hash碰撞。

            static class Entry extends WeakReference<ThreadLocal<?>> {//继承了弱引用*****拿小本本记好,后面有大用
                /** The value associated with this ThreadLocal. */
           //发现并没有前驱节点和后继节点,说明不存在链表和红黑树的结构
           Object value; Entry(ThreadLocal<?> k, Object v) {//同时节点存储的是key是ThreadLocal k, 和一个Object super(k); value = v; } }

    取完ThreadLocalMap之后进行set方法,将当前线程和Value传入。

    注意上面的英文注解:不会使用一个快速的方式例如get方法因为通常使用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);//先hash,计算出来节点所在位置
    
                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;
                    }
                }
            //当前几点是null
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    2.ThreadLocal的get原理

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);//获取当前线程私有的ThreadLocalMap
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);//getEntry()
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();//返回默认值
        }

    getEntry()

    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);//hash
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);//get方法并没有命中,这时候就是发生hash碰撞,也就是当前几点的key并不相同
            }
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e;
                    if (k == null)//处理脏节点
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }

    3.因为是线程私有的所以一定只存储在jvm栈中么?

    其实不是的,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值其实也是被线程实例持有,它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。

    (上面这句话来自 敖丙 的博客,这里感谢 敖丙 大神)

    4.使用ThreadLocal来为每个线程做一个副本,也就是基础使用

    package thread.threadLocal;
    
    /**
     * @ Author     :fqg
     * @ Date       :Created in 17:37 2020/8/19
     */
    public class ThreadLocalTest {
        private static ThreadLocal<Integer> sqNum = new ThreadLocal<Integer>(){
          public Integer initialValue(){//这个方法是重写的,第一次以为是自己定义的,报了空指针
              return 0;
          }
        };
    
        public int getNextNum(){
            sqNum.set(sqNum.get() + 1);
            return sqNum.get();
        }
    
        static class Test extends Thread{
            private ThreadLocalTest sn;
    
            public Test(ThreadLocalTest sn){
                this.sn = sn;
            }
    
            public void run(){
                for(int i = 0; i < 3; i ++){
                    System.out.println("thread [" + currentThread().getName() + "]---->sn[" + sn.getNextNum() + "]");
                }
            }
        }
    
        public static void main(String[] args) {
            ThreadLocalTest sn = new ThreadLocalTest();
            Test t1 = new Test(sn);
            Test t2 = new Test(sn);
            Test t3 = new Test(sn);
            t1.start();
            t2.start();
            t3.start();
        }
    
    }

    结果

    实际使用

    /**sdf有全局变量线程不安全,用ThreadLocal提供线程安全的sdf*/
        public static final ThreadLocal<DateFormat> SDF = new ThreadLocal<DateFormat>() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
            }
        };

    (以上来自Csdn 淘气的高老板)

    5.ThreadLocal的弱引用和内存泄漏问题

    弱引用:如果使用弱引用,每次GC会将只存在弱引用的内存空间进行回收。

    前面两个处理脏节点的注解,为什么一个节点的Key会为null呢?当你存储的时候你是将自己作为key存储进去的啊,怎么会变成null了呢

    ThreadLocal 在没有外部强引用之后,被GC了。所以Entry‘里面的key自然而然就变成了null。然后线程却复用了,也就是说线程却没有消亡value被继续持有,就发生了内存泄漏。

    解决方法:使用remov方法,在每次使用之后调用remove方法来清除其value。

      /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);//hash
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    if (e.get() == key) {//命中
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }

    总结

    使用不多,但是用明白了可以装B

  • 相关阅读:
    SpringBoot第五篇:整合Mybatis
    SpringBoot第四篇:整合JDBCTemplate
    SpringBoot第三篇:配置文件详解二
    分享一篇去年的项目总结
    Oracle生成多表触发器sql
    Oracle 设置用户密码永不过期
    Oracle建表提示SQL 错误: ORA-00904: : 标识符无效
    MySql数据备份
    ETL全量多表同步简述
    ETL全量单表同步简述
  • 原文地址:https://www.cnblogs.com/frank9571/p/13532796.html
Copyright © 2011-2022 走看看