【java多线程】5、ThreadLocal

ThreadLocal

早在JDK1.2的版本中就提供java.lang.Threadlocal, Threadlocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

Threadlocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用 Threadlocal维护变量时, Threadlocal为每个使用该变量的线程分配个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

线程局部变量并不是Java的新发明,很多语言(如 IBM XL、 FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,通过ThreadLocal.的类提供支持。所以,在Java中编写线程局部变量的代码相对来说要笨拙些,这也是为什么线程局部变量没有在Java开发者中得到很好普及的原因

ThreadLocal接口方法

ThreadLoca类接口很简单,只有4个方法,我们先来了解一下。

  • void set(Object value)
    设置当前线程的线程局部变量的值
  • public Object get()
    该方法返回当前线程所对应的线程局部变量
  • public void removed()
    将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK5新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以
    显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度
  • protected Object initialValue()
    返回该线程局部变量的初始值,该方法是一个 protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用geto或 set(Object))时才执行,并且仅执行1次。 ThreadLocal中的默认实现直接返回一个null

值得一提的是,在JDK5.0中, ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是 void set( T value)、T get()以及 T initialvalue()

ThreadLocal是如何做到为每一个线程维护一份独立的变量副本呢?其实实现的思路很简单:在 ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

ThreadLocal与 Thread同步机制的比较

synchronized ThreadLocal
原理 同步机制采用以时间换空间的方式,只提供了一份变量, 让不同的线程排队访问 ThreadLocal采用以空间换时间的方式, 为每一个线程都提供了一份变量的副本, 从而实现同访问而相不干扰
侧重点 多个线程之间访问资源的同步 多线程中让每个线程之间的数据相互隔离

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访间冲突问题,那么,TreadLocal和线程同步机制相比有什么优势呢

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大

而 ThreadLocal则从另一个角度来解决多线程的并发访问。 ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal

由于 ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK50通过泛型很好的解决了这个问题,在一定程度上简化 ThreadLocal的使用,代码清单9-2就使用了JDK5.0新的 ThreadLocal版本。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

public class Test{
    
    
    public static void main(String[] args){
    
    
        // 这个local变量每个线程调用互不影响
        ThreadLocal<String> local = new ThreadLocal<>();
        InStream.Range(0,5).forEach(
            a-> new Thread(
                ()->{
    
    
                    local.set(a+"线程对应的值是"+a);
                    System.out.println(local.get());
                    Thread.sleep(1000);
                }
            ).start();
        )
    }
}

为什么会在数据库连接的时候使用的比较多呢?

class Test2{
    
    
    public 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();
        }
    }
}

上面是一个数据库连接的管理类,我们使用数据库的时候首先就是建立数据库连接,然后用完了之后关闭就好了,这样做有一个很严重的问题,如果有1个客户端频繁的使用数据库,那么就需要建立多次链接和关闭,我们的服务器可能会吃不消,怎么办呢?如果有一万个客户端,那么服务器压力更大。

这时候最好ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。是不是很好用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0BNgx8D-1596734180703)(https://pics3.baidu.com/feed/91ef76c6a7efce1b563edc5501a900dbb58f6512.jpeg?token=a6acac56e087a9c1581a7acfc867015d&s=A642F210061F6DCA0AF341C5030030BB)]

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,内存不够时,会被当成垃圾回收,变为null

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

  • 每个线程里有个threadLocals变量,我们把ThreadLocalThread赋给threadLocals

案例

阿里开发手册上:

【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    
    
    @Override
    protected DateFormat initialValue() {
    
    
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};
说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释: simple beautiful strong
immutable thread-safe。

源码

我是把解析都写到一起了,如果想要分开看推荐https://www.cnblogs.com/fsmly/p/11020641.html ,讲得还不错

public class ThreadLocal<T> {
    
    
    /**
    threadLocalHashCode是当前threadlocal的哈希值,每次调用它的适合都会更新一个新值。
    更新方法为从0开始,每获取一次就+0x61c88647。为什么是这个增量值?因为他哈希值平均
     */
    private final int threadLocalHashCode = nextHashCode();

    // 下一个哈希值,自动更新,从0开始
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
    因为static的原因,在每次new ThreadLocal时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。

0x61c88647是斐波那契散列乘数,它的优点是通过它散列(hash)出来的结果分布会比较均匀,可以很大程度上避免hash冲突,已初始容量16为例,hash并与15位运算计算数组下标结果如下:(16进制取最后一位)
            hashCode	数组下标
            0x61c88647	7
            0xc3910c8e	14
            0x255992d5	5
            0x8722191c	12
            0xe8ea9f63	3
            0x4ab325aa	10
            0xac7babf1	1
            0xe443238	8
            0x700cb87f	15

     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     常量,在实例化完成之前有值即可
     扩展:一个thread内的hashCode是按上面的顺序创建的吗?答案为不是,因为ThreadLocal多线程可以交叉调用
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    // 获取下一个哈希值
    private static int nextHashCode() {
    
    
        // nextHashCode是AtomicInteger类型的,保证了
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    // 当前线程的threadlocalMap中key为threadlocal的默认值,这里默认值为null // 如果想改变默认值,我们可以继承类后重写该方法
    protected T initialValue() {
    
    
        return null;
    }

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    
    
        return new SuppliedThreadLocal<>(supplier);
    }

    // 空构造
    public ThreadLocal() {
    
    
    }

    // 返回当前线程下threadLocalMap里以threadlocal为key的value值
    public T get() {
    
    
        // 得到当前线程
        Thread t = Thread.currentThread();
        // 得到当前线程的ThreadLocalMap // 这个map是在线程里threadlocals变量里存着的,所以任意线程都能拿到他自己的
        ThreadLocalMap map = getMap(t);
        // map是懒加载创建,如果为空时候创建,如果有就直接添加值
        if (map != null) {
    
    
            // map中有多个threadlocal键值对,从map中拿到此threadlocal对应的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                //获取实体e对应的value值,即threadlocal值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
            // 执行到这为找到threadlocal为key的e,所以去给他一个默认值
        }
        // 情况1:map不存在
        // 情况2:map存在,但没有与当前ThreadLocal关联的entry
        return setInitialValue();
    }

    // 情况1:map不存在
    // 情况2:map存在,但没有与当前ThreadLocal关联的entry
    private T setInitialValue() {
    
    
        // 获得默认值
        T value = initialValue();//返回null
        // 获得当前线程
        Thread t = Thread.currentThread();
        // 获得当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
       
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    // 在当前线程的threadLocalMap内添加键值对,key为threadlocal
    public void set(T value) {
    
    
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 实际存储的数据结构类型
        ThreadLocalMap map = getMap(t);
         // 如果存在map就直接set
        if (map != null)
            map.set(this, value);
        else
            // 当前线程Thread不存在ThreadLocalMap对象
            // 则调用createMap进行ThreadLocalMap对象的初始化
            // 并将当前线程t和value作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
    }

    // 判断当前线程的ThreadLocalMap为不为空,不为空就主动移除掉map里的当前threadlocal。需要主动调用remove,否则会有内存溢出,即线程只要不消亡,threadlocal就还在,他的value也在
     public void remove() {
    
    
         // 获取当前线程的ThreadLocalMap
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
    维护了一个ThreadLocalMap
     */
    ThreadLocalMap getMap(Thread t) {
    
     // 参数:当前线程
        // 每个线程维护的threadLocals
        return t.threadLocals; 
    }

    // 第一个存threadlocal的时候创建ThreadLocalMap,这个map与当前线程关联,不需要传入key,因为第一个key是threadlocal
    void createMap(Thread t, T firstValue) {
    
    
        //实例化一个新的ThreadLocalMap,并赋值给当前线程的成员变量threadLocals // 由这个s我们也能体会出每个线程内有多个threadLocal
        // 传入的this和firstValue会作为map中的第一个键值对
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    
    
        return new ThreadLocalMap(parentMap);
    }

    /**
     * Method childValue is visibly defined in subclass
     * InheritableThreadLocal, but is internally defined here for the
     * sake of providing createInheritedMap factory method without
     * needing to subclass the map class in InheritableThreadLocal.
     * This technique is preferable to the alternative of embedding
     * instanceof tests in methods.
     */
    T childValue(T parentValue) {
    
    
        throw new UnsupportedOperationException();
    }
//------------分割线------------------
    /**
     * An extension of ThreadLocal that obtains its initial value from
     * the specified {@code Supplier}.
     */
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    
    

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
    
    
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
    
    
            return supplier.get();
        }
    }
//------------分割线------------------
    /**
    
    ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。。
    ThreadLocalMap是ThreadLocal的静态内部类, 没有实现Map接口, 用独立的方式实现了Map的功能, 其内部的Entry也是独立实现.
To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    //只有内部类可以为static
    /*
     静态内部类和非静态内部类之间区别:
1. 内部静态类不需要有指向外部类的引用。但非静态内部类需要。
2. 静态类只能访问外部类的静态成员,非静态内部类能够访问外部类的静态和非静态成员。
3. 非静态内部类不能脱离外部类实体被创建,非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
    */
    static class ThreadLocalMap {
    
    

        /**
         * 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.
         //Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
//同时让ThreadLocal和储值形成key-value的关系
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
            /** The value associated with this ThreadLocal. */
            Object value;

            // threadlocal为key,值为value
            Entry(ThreadLocal<?> k, Object v) {
    
    
                super(k);
                value = v;
            }
        }

        // 懒加载的初始容量,编译期就确定的常量
        private static final int INITIAL_CAPACITY = 16;

        // 这个数组的容量也必须是2的幂
        private Entry[] table;

        // table内entries的数量
        private int size = 0;

        // 扩容阈值,跟hashmap的阈值同理
        private int threshold; // Default to 0

        //设置阈值为2/3容量,len为容量
        private void setThreshold(int len) {
    
    
            threshold = len * 2 / 3;
        }

        // 获取下一个坐标
        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);
        }

        // 当当前线程第一个调用threadlocal的方法时,创建map,且把传入的值作为第一个threadlocal键值对 // map是懒加载的
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
            // 
            //初始容量为16
            table = new Entry[INITIAL_CAPACITY];
            //位运算,计算出需要存放的位置table[i] // 第一个并不是存在table[0]
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
    
    
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
    
    
                Entry e = parentTable[j];
                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++;
                    }
                }
            }
        }

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        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);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        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;
        }

        /**
        参数:
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be 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);

            //遍历tab如果已经存在则更新值
            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;
            //满足条件数组扩容x2
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
    
    
            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)]) {
    
    
                if (e.get() == key) {
    
    
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
    
    
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
    
    
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
    
    
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        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;
        }

        /**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        private boolean cleanSomeSlots(int i, int n) {
    
    
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
    
    
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
    
    
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
    
    
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

        // 扩容table容量×2
        private void resize() {
    
    
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
    
    
                Entry e = oldTab[j];
                if (e != null) {
    
    
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
    
    
                        e.value = null; // Help the GC
                    } else {
    
    
                        // 获取新的索引位置,原来的位置或者+oldLen的位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            // 如果当前位置有值了就存在下一索引位置
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
    
    
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
    
    
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}
//在某一线程声明了ABC三种类型的ThreadLocal
ThreadLocal<A> sThreadLocalA = new ThreadLocal<A>();
ThreadLocal<B> sThreadLocalB = new ThreadLocal<B>();
ThreadLocal<C> sThreadLocalC = new ThreadLocal<C>();
由前面我们知道对于一个Thread来说只有持有一个ThreadLocalMap,所以ABC对应同一个ThreadLocalMap对象。为了管理ABC,于是将他们存储在一个数组的不同位置,而这个数组就是上面提到的Entry型的数组table。

    那么问题来了,ABC在table中的位置是如何确定的?为了能正常够正常的访问对应的值,肯定存在一种方法计算出确定的索引值i,show me code。

ThreadLocalMap是ThreadLocal的静态内部类, 没有实现Map接口, 用独立的方式实现了Map的功能, 其内部的Entry也是独立实现.

在这里插入图片描述

ThreadLocal不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

public class ThreadLocalTest2 {
    
    

    //(1)创建ThreadLocal变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
    
    
        //在main线程中添加main线程的本地变量
        threadLocal.set("mainVal");
        //新创建一个子线程
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("子线程中的本地变量值:"+threadLocal.get());
            }
        });
        thread.start();
        //输出main线程中的本地变量值
        System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
    }
}

在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    

    protected T childValue(T parentValue) {
    
    
        return parentValue;
    }

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

    void createMap(Thread t, T firstValue) {
    
    
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

ThreadLocal使用不当导致内存泄露

1、基础概念

首先我们先看看ThreadLocalMap的类图,在前面的介绍中,我们知道ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看ThreadLocalMap这个类。在此之前,我们回忆一下Java中的四种引用类型,相关GC只是参考前面系列的文章(JVM相关)

img

2、分析ThreadLocalMap内部实现

上面我们知道ThreadLocalMap内部实际上是一个Entry数组private Entry[] table,我们先看看Entry的这个内部类

/**
     Entry是继承自WeakReference的一个类,
     该类中实际存放的key是指向ThreadLocal的弱引用
     和与之对应的value值(该value值就是通过ThreadLocal的set方法传递过来的值)
     由于是弱引用,当get方法返回null的时候意味着坑能引用
     */
static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
    /** value就是和ThreadLocal绑定的 */
    Object value;

    //k:ThreadLocal的引用,被传递给WeakReference的构造方法
    Entry(ThreadLocal<?> k, Object v) {
    
    
        super(k);
        value = v;
    }
}
//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {
    
    
    super(referent); //referent:ThreadLocal的引用
}

//Reference构造方法
Reference(T referent) {
    
    
    this(referent, null);//referent:ThreadLocal的引用
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    
    
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;//引用队列
}

在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。

总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/107853513