ThreadLocal

结构分析

class Thread {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

public class ThreadLocal<T> {

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //环形数组,线性探测解决冲突
        private Entry[] table;
    }
    
}

常用方法

set

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//获得当前线程的ThreadLocalMap 
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);//还不存在就创建
}

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

初始化ThreadLocalMap的方法:做一些初始工作,并该键值对放入Entry[]中

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

ThreadLocalMap的set方法

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();
        //存在key,直接替换返回即可
        if (k == key) {
            e.value = value;
            return;
        }
        //key已被回收
        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;

    int slotToExpunge = staleSlot;
    //由于将数组看做环形,所以prevIndex
    //从当前位置向前遍历,直到遇到空值,slotToExpunge为遍历过程中最前面的应该替换的旧值
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    //往后遍历
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        // 由于线性探测是向后的,可能找到key存在的元素
        if (k == key) {
            //交换当前的元素和在set()中找到的第一个旧元素的位置
            e.value = value;
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 向前没有找到旧元素,在这个方法中找到的第一个旧位置即为当前位置
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        //当前位置为旧元素,并且在这个方法中之前还没有遇到过旧元素,就将旧元素的首位置记为当前位置
        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 int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 这是旧元素的位置,应该=null help GC
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

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

                //探测
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;//返回遇到的空值的位置
}

//遍历过的i之前的旧元素都清除掉了,从i往后检查logn个位置
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;//返回是否清除过旧元素
}

private void rehash() {
    //清除数组中所有旧元素,因为在之前的遍历中,都是遍历非空的一段,可能隔了一个空元素后面还有旧元素
    expungeStaleEntries();

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

简单描述:

  • 在探测过程中没有无效的元素,并找到了key对应的位置,直接更新即可
  • 在探测过程中存在无效的元素,最终结果是将新元素放置在这个无效元素的位置,并尽可能做清理工作
  • 在探测过程中没有无效的元素且没有找到key,就在空位置放置新元素,并做一次启发式清理和考虑扩容

get

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//获取当前线程的map
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//map还未创建
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocalMap的getEntry()方法

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //直接定位到了即没有hash冲突,直接返回
    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();
        //找到key返回
        if (k == key)
            return e;
        //清除旧元素
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

简单描述:

  • 如果直接定位到了key,直接返回
  • 存在冲突就探测,在探测过程中遇到了无效的元素就做清除,遇到了key就返回
  • 没有找到key,返回null

remove

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

ThreadLocalMap的remove方法

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();//清除引用,key会为null,再清除
            expungeStaleEntry(i);
            return;
        }
    }
}
abstract class Reference<T> {
    public void clear() {
        this.referent = null;
    }
}

内存泄露

一些不会再被使用的对象,却没有被GC,就会造成内存泄露
产生原因:长生命周期的对象持有短生命周期的引用

内存泄露的例子:

public static void main(String[] args) {
    List<Integer> list=new ArrayList<>();
    Integer o =1;
    list.add(o);
    o=null;
    System.out.println(list);
}

堆中的o由于存在list对它的引用,所以不会被GC回收,但是却不需要再使用它了,会造成内存泄露
解决内存泄露的原则:减小作用域和手动设置null

手动设置null参考ArrayList的remove()方法

public E remove(int index) {
    //....
    System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

ThreadLocal内存泄露原因

ThreadLocal自身并不储存值,而是作为一个key来让线程从ThreadLocal获取value
Entry是中的key是弱引用,所以jvm在垃圾回收时如果外部没有强引用来引用它,ThreadLocal必然会被回收
但是,作为ThreadLocalMap的key,ThreadLocal被回收后,ThreadLocalMap就会存在key为null,但value不为null的Entry

  • 若当前线程一直不结束,可能是作为线程池中的一员,线程结束后不被销毁
  • 分配(当前线程又创建了ThreadLocal对象)使用了又不再调用get/set方法,就可能引发内存泄漏
  • 就算线程结束了,操作系统在回收线程或进程的时候不是一定杀死线程或进程的,在繁忙的时候,只会清除线程或进程数据的操作,重复使用线程或进程(线程id可能不变导致内存泄漏)

因此,key弱引用并不是导致内存泄漏的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,并且没有手动删除对应key。

那么,为什么要将Entry中的key设为弱引用?相反,设置为弱引用的key能预防大多数内存泄漏的情况

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

避免内存泄露的方法:在使用完ThreadLocal时,及时调用它的的remove方法清除数据。

InheritableThreadLocal

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
   @Override
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

对这样一段程序

public static void main(String[] args) {
    ThreadLocal<Integer> threadLocal=new ThreadLocal<>();

    threadLocal.set(1);
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//null
        threadLocal.set(2);
        System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//2

    }).start();
    System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
}

若是用的InheritableThreadLocal

public static void main(String[] args) {
    ThreadLocal<Integer> threadLocal=new InheritableThreadLocal<>();

    threadLocal.set(1);
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
        threadLocal.set(2);
        System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//2

    }).start();
    System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
}

可以看到Thread 0得到了main线程中设置的值

Thread类中的init方法

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //.....
    Thread parent = currentThread();
    //....
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    //...
}

可以看到子线程在创建的时候已经获得了父线程的inheritableThreadLocals

ThreadLocal的createInheritedMap方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
//复制ThreadLocalMap中所有不为空且key!=null的元素
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++;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值