针对ThreadLocal我总想说点什么,以及使用场景

说起ThreadLcoal这个清楚又陌生的词,由于我在公司里也没有什么高并发的场景,只知奇然而不知其所以然,并受到了打击,像我这种天天除了curd其他什么都不会的人还是想了解一下这个ThreadLocal具体到底是干嘛的,并且具体目的还是想自己总结点东西想与别人吹吹牛逼,仅此而已。虽然这只是java里的皮毛。

好了正式开始.
ThreadLocal我也不想去背书,因为我也记不住,那他具体是怎么回事呢?
首先我们先看看这个对象是咋new出来的
ThreadLocal threadlocal = new ThreadLocal;

官方其实已经解释非常清晰明了了,就是维护当前线程内的局部变量,为什么这么说呢,看源码就能知道其所以然

   //这是ThreadLocal内部的set方法
   public void set(T value) {
  		//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap,由此可知Thread内部竟然还维护了一个ThreadLocalMap 对象
        ThreadLocalMap map = getMap(t);
        //判断当前对象是否为null
        if (map != null) {
        	//如果不为null那么就把当前对象作为key,值作为value扔到这个map里
            map.set(this, value);
        } else {
           //
            createMap(t, value);
        }
    }
  
   //这个对象其实就是获取当前线程内部的ThreadLocalMapMap  
   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    //如果当前线程内部的ThreadLocalMap为null 那么就自己初始化一个
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    } 

丛上面的代码中我们可以看到,Thread 里面竟然还维护一个ThreadLocalMap,那么这个破玩应到底是干嘛的,它其实是ThreadLocal的一个静态内部类,他的作用简单的来说就是用来缓存当前线程的一个大map(首次会初始化当前线程的ThreadLodalMap,下次直接从当前线程中获取),重点就在这里看下面的源码
注意这是ThreadLocal内存泄露的原因

 //重点来了哇,我靠存储一个弱引用
 static class Entry extends WeakReference<ThreadLocal<?>> {
            //对比弱引用这竟然是个强引用
            Object value;
            
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

上面是什么鬼为啥有强有弱,跟据java虚拟机的情况我们知道在java虚拟机里,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。而对于强引用,如果一个对象具有强引用,那垃圾回收器绝不会回收它.

我靠重点又来了,我的ThreadLocal还在用,我还在往里放值呢,那么当我java虚拟机的内存不够用了,即使你把key回收了,那我的value还在,岂不是要oom(内存泄露了),

那么问题有来了呀,为甚么jdk元老还要这么设计

key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除。

比较以上两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。

原来如此豁然开朗,牛逼吹的可以

我靠,我又有疑问了,线程过多的话,那他是怎么保证这个map的hash不会冲突呢,你哪里来的这么多问题?因为我的疑问很多哇
那么我们看看下面的源码
这是ThreadLocalMap的源码

       //下一个索引
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 上一个索引.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

卧槽这是什么鬼,
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

副本是怎么回事,这个入口在哪里呢,丛ThreadLocal内部的静态类ThreadLocalMap构造函数入手吧

      //这是一个公共构造,就是上面默认创建的
	  ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
		
		//这是一个私有构造根据官网解析,这个只供ThreadLocal内部createInheritedMap方法调用
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

ThreadLocal内部方法(忽略inheritableThreadLocals调用现成的Map创建方式而已)
丛官网上看只供Thread调用,卧槽绕来绕去又回到了Thread

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

到这里又恍然大悟,原来Thread里竟然维护了一个ThreadLocalMap,估计晚上不用睡觉了,那么让我们来看看Thread源码

 //第一次调用
 public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
 //第二次调用注意最后一个参数inheritThreadLocals=true
 public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        this(group, target, name, stackSize, null, true);
   }
 //第三次调用 只看中文
 private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
    
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        
        g.checkAccess();

        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(
                        SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            //维护父集的Map 值
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
                this.stackSize = stackSize;
        this.tid = nextThreadID();
    }     

原来这是一切的入口,那么接下来我们抛弃以前所有的东西,继续往下看ThreadLocalMap里的私有构造

 private ThreadLocalMap(ThreadLocalMap parentMap) {
           
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

丛上面我们可以看出
1、Thread 拥有属于自己的一个map,key为 ThreadLocal,value为值
2、ThreadLocal 获取值时实际上是从当前 Thread 的map中获取(以自己为key)

这也就是为什么 ThreadLocal 能在每个 Thread 中保持一个副本,实际上数据是放在 Thread 中的。

取值没有说附上源码,今天实在熬不动了,改天再说

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

  private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
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;
        }
 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

研究了一晚上终于对ThreadLocal了解一点点了,
那么简单的概述一下就是在Tread创建的时候,ThreadLocalMap其实就已经存在了,只不过是为了不改变原来的基础上复制出来一份,放到ThreadLocal里面,作为副本便于我们操作,而真正的值却没有改变!

使用场景

比如数据库连接,或日期转化SimpleDateFormate
这篇文章很好可以看一下
https://www.cnblogs.com/zz-ksw/p/12684877.html

大家如果在看完以后,我如果哪里搞错了,可以在评论区里探讨,明天继续攻克java的基础知识点.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值