1.6、JDK 源码分析 - ThreadLocal

原创
2022/07/01 22:59
阅读数 336

摘要

工作中,经常会遇到很多场景有使用线程级别变量的这种情况(线程隔离)!本篇博文主要带大家来认识以下这个神秘ThreadLocal相关的应用知识以及底层原理;

介绍

根据JDK源码注释,该类ThreadLocal主要用于提供线程本地变量。这些变量与普通变量的不同之处在于,每个访问变量的线程都有自己独立初始化的变量副本(通过其*{@code get}或{@code set}方法)。 实例通常是类中希望将状态与线程关联的私有静态字段 (例如,用户ID或事务ID)。

例如,下面的类为每个线程生成本地唯一标识符。线程的id在它第一次调用{@code ThreadId.get()}时被分配,并且在后续调用中保持不变 。

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
            new ThreadLocal<Integer>() {
                @Override
                protected Integer initialValue() {
                    return nextId.getAndIncrement();
                }
            };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}



只要线程处于活动状态且ThreadLocal实例可访问,每个线程都持有对其线程本地变量副本的隐式引用;线程消失后,其线程本地实例的所有副本都将接受垃圾收集(除非存在对这些副本的其他引用)。

主要方法

ThreadLocal主要有一个空的构造器方法、一个受保护的方法和四个public方法:ThreadLocal()、initialValue()、get()、set(T value) 、remove()、withInitial(Supplier<? extends S> supplier)

initialValue()方法

protected T initialValue() {
        return null;
}



返回此线程本地变量的当前线程“初始值”。当线程第一次使用{@link#get}方法访问变量时,将调用此方法,除非线程之前调用了{@link#set}方法,在这种情况下,不会为线程调用{@code initialValue}方法。通常,每个线程最多调用一次该方法,但在随后调用{@link#remove}和{@link#get}的情况下,可以再次调用该方法。 <p>这个实现只返回{@code null};如果程序员希望线程本地变量具有除{@code null}}之外的初始值,{@code ThreadLocal}必须被子类化,并且这个方法被重写。 通常,将使用一个匿名内部类。

get()方法

//返回当前线程本地变量副本中的值。 如果变量没有当前线程的值,它首先被初始化为调用{@link #initialValue}方法返回的值。
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
//为线程本地变量设置初始值
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
//获取线程本地变量表
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
 }
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}



要理解这个get方法,咋们还得看看Thread这个类,其实在Thread类里面声明了一个变量map用于保存所有线程本地变量;

ThreadLocal.ThreadLocalMap threadLocals = null;

它的初始化就是在客户端调用ThreadLocal.get()方法时进行;

:ThreadLocal中调用创建ThreadLocalMap时,传递了当前ThreadLocal实例的引用this作为key,此处且看下文分析;

set(T value) 方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}



将这个线程本地变量的当前线程副本设置为指定的值。 大多数子类将不需要重写这个方法,只依赖{@link #initialValue}方法来设置线程本地变量的值。

remove() 方法

public void remove() {
    ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}



移除此线程本地变量的当前线程值。 如果这个线程本地变量随后被当前线程{@linkplain #get read}调用,它的值将通过调用它的{@link #initialValue}方法重新初始化,除非它的值在过渡期间被当前线程{@linkplain #set set}调用。 这可能会导致在当前线程中多次调用{@code initialValue}方法。

withInitial(Supplier<? extends S> supplier)方法

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

 /**
     * An extension of ThreadLocal that obtains its initial value from
     * the specified {@code Supplier}.
     * ThreadLocal的一个扩展,它从指定的{@code supplier供应商}获取初始值;此类是ThreadLocal的一个静态内部类;
     */
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();
    }
}



创建一个线程本地变量。 变量的初始值是通过调用{@code Supplier}上的{@code get}方法来确定的。 @param <S>线程本地值的类型 @param supplier用于确定初始值的供应商 返回一个新的线程本地变量 如果指定的供应商为空,则抛出NullPointerException

package java.util.function;

/**
 * Represents a supplier of results.(表示结果的提供者)
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.(并不要求每次调用供应商时都返回一个新的或不同的结果 )
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #get()}.(这是一个函数式接口,其函数方法是{@link #get()}。 )
 *
 * @param <T> the type of results supplied by this supplier(该供应商提供的结果类型)
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}




其实以上看着很复杂,实际就是ThreadLocal提供的一个函数式编程接口实现初始化的方式(Lambda构造方式); 具体用法如下:

import java.util.concurrent.atomic.AtomicInteger;

public class TestThreadlocal {
  private static final AtomicInteger nextId = new AtomicInteger(0);
  private static ThreadLocal<Integer> local = ThreadLocal.withInitial(()->nextId.getAndIncrement());

  public static void main(String[] args) throws InterruptedException {
    Runnable r = new TT();
    new Thread(r, "thread1").start();
    Thread.sleep(2000);
    new Thread(r, "thread2").start();
  }

  private static class TT implements Runnable {
    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName()+" set local name and get: "+ local.get());
    }
  }
}




createInheritedMap方法

    /**
     *工厂方法来创建继承的线程本地变量的映射。 设计为只能从线程构造函数调用。
     *
     * @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);
    }

    /**
     *方法childValue是在子类InheritableThreadLocal中明显定义的,但这里是在内部定义的,以便提供createInheritedMap工厂方法,
     *而不需要在InheritableThreadLocal中创建映射类的子类。
     *这种技术比在方法中嵌入测试实例更可取。
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }



内部类知识

ThreadLocal主要有两个内部类:ThreadLocal.SuppliedThreadLocal、ThreadLocal.ThreadLocalMap.

ThreadLocal.SuppliedThreadLocal内部类

该内部类在前面讲withInitial(Supplier<? extends S> supplier)方法的时候已经提到,它是ThreadLocal内部实现的一个自己的子类,该内部类通过构造函数包装一个supplier函数式接口,该接口定义了一个get方法;而在重写initialValue()方法时,调用类传入进来的supplier函数实例的get方法返回;换句话说-它是用于协助ThreadLocal实现Lambda构造方式的一个核心类;具体如下:

protected T initialValue() {
    return supplier.get();
}



ThreadLocal.ThreadLocalMap内部类

ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。 没有操作被导出到ThreadLocal类之外。 这个类是包私有的,允许在类Thread中声明字段。 为了帮助处理非常大和长期的使用,散列表条目使用WeakReferences作为键。 但是,由于没有使用引用队列,所以只有在表空间耗尽时才保证删除过时的条目。

该静态内部类中定义Entry这个静态内部类

    static class ThreadLocalMap {

        /**
         * 这个散列映射中的条目扩展WeakReference(弱引用),使用它的主引用字段作为键(始终是ThreadLocal对象)。 注意,空键(即entry.get() == null)意味着该            * 键不再被引用,因此条目可以从表中删除。 这样的条目在下面的代码中称为“陈旧条目”。
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }



ThreadLocalMap源码中可以看到,每个线程局部变量的数据其实最终以Entry对象存放在ThreadLocalMap的一个Entry[]数组中;

我们在之前的ThreadLocal.get()方法中,已经了解到--在客户端第一次调用get()方法,会以ThreadLocal实例对象作为key,ThreadLocal的initialValue()方法返回结果作为value作为参数初始化一个ThreadLocalMap返回并且设置到当前线程的threadLocals字段上面;

ThreadLocal.ThreadLocalMap构造方法

        /**
         * The number of entries in the table.(当前数组容器中存放的entry数)
         */
        private int size = 0;

        /**
         * The next size value at which to resize.(要调整大小的下一个大小值,阈值)
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.(将调整大小阈值设置为最坏保持2/3的负载因子)
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 初始化容量大小 -- 必须是2的幂.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, 根据需要调整表的大小.
         * table.length MUST 必须是2的幂.
         */
        private Entry[] table;
        /**
         * 构造一个最初包含(firstKey, firstValue)的新map
         * ThreadLocalMaps是惰性构造的,所以我们只在至少有一个条目要放入的时候才创建一个该对象
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //初始化容量为16的一个Entry数组作为容器存放数据
            table = new Entry[INITIAL_CAPACITY];
            //找到对应的hash槽位
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //设置数组该位置的项,key为ThreadLocal实例,value则为ThreadLocal.initialValue()方法返回值
            table[i] = new Entry(firstKey, firstValue);
            //设置当前数组的真实size
            size = 1;
            //初始化扩容阈值16*2/3=10
            setThreshold(INITIAL_CAPACITY);
        }



ThreadLocal 哈希计算分析:

根据源码可以看出,ThreadLocalMap计算hash槽位的方法定义在ThreadLocal中;

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

此处的&是Java中按位与运算符;如果此处不太明白,可以先补充下''位运算''相关知识

    /**
     * ThreadLocals依赖于附加到每个线程的每个线程的线性探针哈希映射(thread。 threadlocal和inheritablethreadlocal)。 ThreadLocal对象充当键,  
     * 通过threadLocalHashCode搜索。 这是一种自定义散列代码(仅在ThreadLocalMaps中有用),它可以在相同线程使用连续构造的ThreadLocals的常见情况下消      * 除冲突,而在不常见的情况下保持良好的行为。 
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.(下一个要给出的哈希码。 自动更新。 从0开始。 )
     */
    private static AtomicInteger nextHashCode =  new AtomicInteger();

    /**
     * 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.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.(返回下一个散列码)
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }



从上面的代码可以看出ThreadLocal的每个实例的threadLocalHashCode都不一样,并且只在创建ThreadLocal实例的时候初始化一次;

ThreadLocal.ThreadLocalMap.getEntry方法

该方法用于根据ThreadLocal实例作为key获取线程本地变量map中对应的entry对象,如果根据hash获取到的entry里面的key和当前传进来的key不是同一个,则转到getEntryAfterMiss方法依次从当前定位的槽位往后查找对应的entry, 后续步骤如果遇到已过期的条目就删除该过期槽位的entry项,并检查并处理当前槽位开始往后(环形检测,即如果到了表末端还未遇到空槽位,则会返回到表头继续往后检测)所有entry是否过期,直到遇到一个槽位entry为null为止.

        /**
         * 获取与key相关联的条目。 这个方法本身只处理快速路径:直接命中存在的key。 否则它会转发到getEntryAfterMiss。 
         *
         * @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);
        }

        /**
         *  getEntry方法的版本,用于在直接哈希槽中找不到键时使用。
         *
         * @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)
                    //k==null表示,key已经被回收(注:key是保存在弱引用对象entry中的)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * 通过重新散列位于staleSlot和下一个空槽之间的任何可能冲突的条目来删除陈旧的条目。 这还会删除尾随null之前的任何陈旧条目。 参见Knuth,章节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 (重新hash,直到遇到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;
                        // 与Knuth 6.4算法R不同,我们必须扫描到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;
        }
        /**
         * Increment i modulo len.  i对len取模
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }


ThreadLocal.ThreadLocalMap.set方法

该方法用于设置线程本地变量的值,其过程中如果遇到弃用的槽位会进行清理,并且当在新的槽位插入后,还会触发清理过期条目动作,并根据size大小判断是否进行扩容

        /**
         * 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) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //按顺序从i槽位向后面遍历
            for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果找到k对应的项,则重新赋值
                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();
        }
        //替换过期的项
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,  int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // 备份以检查当前运行中先前的陈旧条目。
            // 我们一次清理整个线,以避免由于垃圾收集器释放了成组的ref(即每当收集器运行时)而导致的持续的增量重散列。
            int slotToExpunge = staleSlot;
            // 以 staleSlot 为基础,向前查找到最前面一个弃用的哈希槽,并确立清除开始位置
            for (int i = prevIndex(staleSlot, len);  (e = tab[i]) != null; i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // 查找run的键或尾随的空槽,以先出现的为准
            // 以 staleSlot 为基础,向后查找已经存在的 ThreadLocal
            for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null; i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();


                 //如果我们找到了key,那么我们需要将它与陈旧的条目交换,以维护哈希表的顺序。 然后,
                 //可以将新过时的插槽或在其上面遇到的任何其他过时插槽发送到expungeStaleEntry,以删除或重新散列运行中的所有其他条目。
                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);
        }
        //向前环形遍历
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        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;
        }


ThreadLocal.ThreadLocalMap.remove方法

该方法用于删除一个线程本地变量值,其中也会涉及到清理弃用槽位

        /**
         * 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;
                }
            }
        }

//clear方法定义在JDK的引用抽象类中
public abstract class Reference<T> {
   ...
   public void clear() {
        this.referent = null;
    }
   ...
}


ThreadLocal.ThreadLocalMap.扩容方法

在调用set时,如果在新槽位插入,会检查当清理一些过时的槽位;此时,如果剩下的已经使用的槽位达到了扩容的阈值,就会调用 rehash();并且将容量扩为之前的两倍;因为初始容量为16,所以ThreadLocalMap的容量一直是2的幂

        //重新包装和/或重新设置大小的表。 首先扫描整个表,删除过时的条目。 如果这不能充分收缩表的大小,则将表的大小加倍。 
        private void rehash() {
            //删除过时项
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }
        /**
         * Double the capacity of the table.(扩容为之前的两倍,并且将旧表的值,插入到新表)
         */
        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 {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

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


InheritableThreadLocal

InheritableThreadLocal提供可继承的线程本地变量,它是ThreadLocal的子类;当在变量中维护的每个线程属性(例如,用户ID、事务ID)必须自动传输给创建的任何子线程时,可继承的线程本地变量优先于普通线程本地变量。

定义如下:

/**
 * 这个类扩展了<tt>ThreadLocal</tt>来提供从父线程到子线程的值继承:当创建子线程时,子线程接收到父线程有值的所有可继承的线程本地变量的初始值。
 * 通常,子进程的值与父进程的值是相同的; 但是,通过重写这个类中的<tt>childValue</tt>方法,子节点的值可以用任意函数去映射父节点的值获得。
 */

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     *为这个可继承的线程本地变量计算子线程的初始值,作为子线程创建时父线程值的函数。 在子线程启动之前,从父线程内部调用此方法。
     *此方法仅返回其输入参数,如果需要不同的行为,则应该重写此方法。
     *
     * @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);
    }
}


使用案例:

import java.util.concurrent.atomic.AtomicInteger;

public class TestThreadlocal1 {
  private static final AtomicInteger nextId = new AtomicInteger(0);
  private static ThreadLocal<Integer> local = ThreadLocal.withInitial(()->nextId.getAndIncrement());

  public static void main(String[] args) throws InterruptedException {
    Runnable r = new TT();
    new Thread(r, "thread1").start();
    Thread.sleep(2000);
    new Thread(r, "thread2").start();
  }

  private static class TT implements Runnable {
    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName()+" set local name and get: "+ local.get());
    }
  }
}



最后总结:

1、ThreadLocalMap底层数据结果是数组实现,数组的类型为Entry,Entry项继承自WeakReference(JDK弱引用),Map的key是ThreadLocal实例对象(存储在entry继承的弱引用中),并且Entry扩展了一个value字段用于保存值;

2、ThreadLocalMap在解决hash冲突的时候和HashMap不同,它没用使用链表,而是直接以定位到的槽位为初始向后进行探测,如果探测到可用槽位则插入值;同时中间过程伴随着过时entry清理,以及扩容,向前移动等等一系列的操作;

3、ThreadLocal 通过线程独占的方式,也就是隔离的方式,避免了多线程问题;

4、在使用 ThreadLocal 的时候最好手动移除,以避免内存泄漏;

扩展知识:

说明:关于引用相关的源码 Reference、StrongReference、SoftReference、WeakReference、PhantomReference将在下一篇博文中进行分享

类型 回收时间 应用场景
强引用 一直存活,除非GC Roots不可达 所有程序的场景,基本对象,自定义对象等
软引用 内存不足时会被回收 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存
弱引用 只能存活到下一次GC前 生命周期很短的对象,例如ThreadLocal中的Key。
虚引用 随时会被回收, 创建了可能很快就会被回收 可能被JVM团队内部用来跟踪JVM的垃圾回收活动
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部