ThreadLocal重新理解上路

前言

之前基于对深入理解JVM的理解,对于ThreadLocal的理解是存在弱引用,从而使得避开了本次的GC,而并没有从数据结构的层面分析,ThreadLocal的内部实现原理,本次重新上路就是针对ThreadLocal的数据结构而做出分析,从设计原理到JVM的回收,以及如何保证多线程的场景下实现互不干预的效果。

ThreadLocal的应用概念

首先,ThreadLocal是有节点概念,而这个节点的作用是多线程之间互不干预的前提,相当于每个线程都有自己的作用节点的ThreadLocal副本,其次,ThreadLocal的源码里面是有ThreadLocalMap这个概念,而这个Map是跟java.util.Map,是两个完全不同 的概念,再次,声明一点就是,这个Map里面的存在K-V的关系,而K就是弱引用指向“环形的ThreadLocal表或者称之为ThreadLocal数组”。
理解了这三个基本的概念的基础上,下面就开始从源码的角度来探索ThreadLocalMap的定义
这是ThreadLocal的其中一个API 源码,相信很多朋友都明白他的使用,而这个get方法直接就初始化了ThreadLocalMap,而且这里引出了另外一个概念就是环形数组的概念,也就是ThreadLocalMap.Entry 的使用。

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

下面在看看Entry的源码可以看到,是继承了WF,也就是弱引用,这里就引出了避开本次的GC而产生OOM 的情况以及,每个线程都运行在各自的ThreadLocal的副本里面,而这个线程作用的变量存放到ThreadLocalMap里面的强引用的Value。

/**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;
            //这里的k是指向环形ThreadLocal数组的引用,而v是指向堆中的值的强引用。
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

TheadLocal产生OOM的原因

正如上面的源码可以看到,只要存在着弱引用的k都可以指向环形TL数组,而这里的环形的数组都运行着各自的作用线程,这样就会使得线程所在的内存空间的变量,不会被本次GC回收,所以这几是产生OOM的原因,而解决这个情况的方案,从可达性考虑,减少环形数组的长度,其次就是减少新生代GC的次数,进行JVM调优处理。

ThreadLocal的使用时机

既然存在这样的机制,使用ThreadLocal是基于线程之间的隔离,对变量互相不干扰,适合多线程的场景下使用,比如笔者目前有个商城的活动里面,高并发场景使用到redis锁来控制高并发的超买超卖的情况,然而,这里的实现并不如意,由于关联到了卡券的激活处理,调用到第三方的接口,长连接的情况下,redis锁超时了,而同一个ID进行的频繁的同样的操作,导致了redis锁不能正常的锁定商品。
如果使用ThreadLocal来解决这种多线程高并发的商城业务,可以设计为,(前提是商城里面的商品数量不能太大,如果想天猫,京东这种就不适合使用这种方案解决问题),ThreadLocal数组存放的是商品的id以及商品的数量,而ThreadLocalMap的value就存放商品的总数,再使用redis锁控制高并发,这样每个线程的操作都在自己的内存作用的ThreadLocal,而最后操作的是Value的值,就不会出现一个人多买的情况,因为ThreadLocal的值都是指定为商品没人限定的购买的次数。具体的代码实现后续下节发布。

你可能感兴趣的:(代码技巧,高并发,ThreadLocal)