• ThreadLocal解析


    在面试中,ThreadLocal是一个经常会被问到的知识点,所以今天就好好分析一下它的实现原理。(基于JDK1.8)

    什么是ThreadLocal

    ThreadLocal是一个数据结构,类似于HashMap,可以保存key:value键值对,但是一个ThreadLocal只能保存一个,并且各个线程之间的数据互不干扰。

    看下它的set(T value)get()方法的源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    }



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

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

    可以看到每个线程中都会有一个ThreadLocal数据结构,set方法是将值保存到当前线程中的threadLocals变量中,get方法是从当前线程中的threadLocals变量中获取。所以对于线程1中的数据在线程2中是无法访问的,保证了线程之间的互不干扰。

    那接下来就看其中的关键类ThreadLocalMap是什么了。

    ThreadLocalMap

    ThreadLocalMap是ThreadLocal的一个静态内部类。它也是Thread类的一个属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
      static class  {

    static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

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



    * 初始化大小,必须是二的幂.
    */
    private static final int INITIAL_CAPACITY = 16;

    大专栏  ThreadLocal解析ine">
    * The table, resized as necessary.
    * table.length MUST always be a power of two.
    */
    private Entry[] table;


    * The number of entries in the table.
    */
    private int size = 0;


    * The next size value at which to resize.
    */
    private int threshold; // Default to 0


    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中,初始化了一个大小为16的Entry数组,Entry对象用来保存key-value键值对,只不过在这里key永远都是ThreadLocal对象(map.set(this, value);)。

    Entry继承的是WeakReference,并且Entry中没有next字段,所以不会形成链表。

    Thread、ThreadLocal和ThreadLocalMap的关系

    set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    private void set(ThreadLocal<?> key, Object value) {
    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)]) {
    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;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();
    }

    内存泄漏

    由于Entry是继承WeakReference,这就导致了在ThreadLocal没有外部强引用时,发生GC时就会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

    如何避免内存泄露

    remove方法,如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

    1
    2
    3
    4
    5
    6
    ThreadLocal<String> localName = new ThreadLocal();
    try {
    localName.set("哈哈");
    } finally {
    localName.remove();
    }
  • 相关阅读:
    问答
    正在设计taijilang的解析器,真可谓尸横遍地
    因为这些理由而坚持用grunt?其实它们都不成立。
    开始设计taijijs
    从grunt转到gulp
    google 索引
    :: operator
    用coffeescript写构造函数
    jade与angular.js
    angular.js 资料收集
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12041339.html
Copyright © 2020-2023  润新知