ThreadLocal原理分析

一、概述

ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

二、实现原理

ThreadLocal相关UML类图:

 由该图可知,Thread类中有threadLocals和inheritableThreadLocals两个ThreadLocal.ThreadLocalMap类型的变量,二ThreadLocal.ThreadLocalMap是一个定制化的HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或get方法是才会创建它们。其实每个线程的本地变量不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来。当调用线程调用它的get方法是,再从当前线程中的threadLocals变量里面将其拿出来使用。如果调用线程一直不终止,name这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。另外,Thread里面的threadLocals为何设计为map结构,是因为每个线程可以存储多个ThreadLocal变量。

1、set方法:

public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//以当前线程为key,获取当前线程变量
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);//初始化线程threadLocals
    }
//    
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//    
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap对象(以当前ThreadLocal对象为key(firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)),firstValue为value)初始化;赋值给当前线程
    }

 2、get方法

public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程变量
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//Hashmap取值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);//计算数组坐标
            Entry e = table[i];
            if (e != null && e.get() == key)//计算key值
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

3、remove方法

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

//ThreadLocalMap移除变量   
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;
                }
            }
        }

 ThreadLocal不支持继承性,有关更多内容可以查看我的文章《Java线程变量问题-ThreadLocal》,地址:https://www.cnblogs.com/wangymd/p/11012658.html

三、ThreadLocal内存泄露问题

ThreadLocalMap使用ThreadLocal的弱引用未key,而value是强引用。如果ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。如果不做处理的话,value永远无法被GC回收,此时就可能会产生内存泄露。虽然ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录,但是建议使用完ThreadLocal方法后手动调用remove()方法。

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)//查询到Entry
                return e;
            else
                return getEntryAfterMiss(key, i, e);//未查询到Entry
        }
        
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)//key为null情况
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
        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) {//key为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;
        }
原文地址:https://www.cnblogs.com/wangymd/p/12980915.html