ThreadLocal讲义

ThreadLocal

线程局部变量,属于线程自己本身的变量,对于其他线程是隔离,不可见的

线程变量存储在哪里数据结构里面呢?进入thread类,

public class Thread implements Runnable {

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    //线程存储变量的容器map,在使用到局部变量时才会初始化
	ThreadLocal.ThreadLocalMap threadLocals = null;
	
    //.........省略部分代码..........
}

问题一:什么时候线程的这个map才会初始化呢?

怀揣着这个疑问,开始使用threadlocal

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(7);

我们发现 new ThreadLocal<>() 没有初始化线程的map,进入set方法看看

//设置线程变量
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的Map容器
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //this为threadlocal实例
        map.set(this, value);
    else
    	//创建map
        createMap(t, value);
}

//初始化线程容器map
 void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

发现在线程第一次set 变量时,才会创建一个map来存储

问题二:线程变量存储在哪里?

进入new ThreadLocalMap(this, firstValue) ,看看变量具体是存储在 ThreadLocalMap 中的哪个数据结构

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //默认容量16
    table = new Entry[INITIAL_CAPACITY];
    //通过threadlocal实例的hash值计算出在entry数组中存放的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //把数据放入到entry中
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

我们发现数据是存放在数据结构 entry 中,进入entry,看看这个数据结构是怎样的

//ThreadLocal 被弱引用包裹,当发生GC时,threadlocal将自动被清除
static class Entry extends WeakReference<ThreadLocal<?>> {
	//线程变量存放在这个value中
    Object value;
    
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
public abstract class Reference<T> {
	//引用key被存放到这里
    private T referent; 

	//.....省略部分代码..........
}

发现线程变量存放在 entry 这个数据结构的 value 属性中,而这个key(也就是threadlocal实例),却被弱引用包裹,存放在Reference 的 referent 属性中, 当发生GC时,threadlocal 实例将自动被清除

问题三:很多文章都说threadlocal会造成内存泄漏呢,那究竟是怎么一个泄漏法呢

按照上面的流程,我们知道了,线程变量的值是存储在 entry 的 value 中,而threadlocal实例被WeakReference 装饰,也就是当发生GC时,threadlocal 实例将自动被清除,如果这个 threadlocal 实例被GC回收了,可是entry 中的 value 属性值 却和 真实的内存对象存在 强引用 关系,也就是说,这个没用的entry对象是无法被GC回收的
ThreadLocal讲义_第1张图片

问题四:很多文章都说threadlcoal的get、set 方法会自动清除 key=null 的 entry 对象,那岂不是不会产生内存泄漏了????

伴随着上面的疑问,我们看看threadlocal的 get、set 方法

 private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //通过threadlocal实例的hash值计算出在entry数组中存放的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        //该位置已经存放着一个threadlocal实例
        ThreadLocal<?> k = e.get();

        //是同一个threadlocal实例,直接覆盖之前的线程变量,结束
        //所以一个threadlocal实例只能存放一个线程变量,要存放多个线程实例,就需要多个threadlocal实例
        if (k == key) {
            e.value = value;
            return;
        }
		//如果之前的threadlocal实例由于GC变为null,先把之前entry的value置空,再在当前位置存放一个新创建的entry对象,结束
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    //这个位置的entry数组是空的,直接把线程变量变成存储在entry中
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


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

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //是当前的threadlocal实例,直接返回
    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();
        //是当前的threadlocal实例,直接返回
        if (k == key)
            return e;
        //当key 被回收时,
        if (k == 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
    //把当中这个key为null的entry的vlaue置空
    tab[staleSlot].value = null;
    //清空当前位置数组中的引用
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    //把数组中所有的key=null的entry的value清空,然后再清空数组中的引用
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
        	//把当中这个key为null的entry的vlaue置空
            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;
}

发现如果一直都是同一个threadlocal在 get、set ,是不会清除key为null的entry的,也就是说 ,是存在内存泄漏的可能的
所以在使用完threadlocal之后,最好手动调用 remove 方法来清除当前threadlocal实例以及线程变量值

//移除掉当前threadlocal实例以及当前实例对应的线程变量值
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    //通过threadlocal实例的hash值计算出在entry数组中存放的位置
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //调用WeakReference的clear方法清除对当前ThreadLocal实例的弱引用,也就是设置entry的key为null
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

ThreadLocal源码分析

public class ThreadLocal<T> {
    //设置线程变量
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的Map容器
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //this为threadlocal实例
            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();
    }
    
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    //初始化线程容器
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    //获取线程的map容器
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    //threadlocal的静态内部类ThreadLocalMap
    static class ThreadLocalMap {
        private Entry[] table;
        
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //默认容量16
            table = new Entry[INITIAL_CAPACITY];
            //通过threadlocal实例的hash值计算出在entry数组中存放的位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //把数据放入到entry中
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        
        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            //通过threadlocal实例的hash值计算出在entry数组中存放的位置
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                //该位置已经存放着一个threadlocal实例
                ThreadLocal<?> k = e.get();

                //是同一个threadlocal实例,直接覆盖之前的线程变量,结束
                //所以一个threadlocal实例只能存放一个线程变量,要存放多个线程实例,就需要多个threadlocal实例
                if (k == key) {
                    e.value = value;
                    return;
                }
				//如果之前的threadlocal实例由于GC变为null,直接替换掉之前的实例,结束
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            //这个位置的entry数组是空的,直接把线程变量变成存储在entry中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        //移除掉当前threadlocal实例以及当前实例对应的线程变量值
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            //通过threadlocal实例的hash值计算出在entry数组中存放的位置
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    //调用WeakReference的clear方法清除对当前ThreadLocal实例的弱引用,也就是设置entry的key为null
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
        
        //删除旧的entry
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // 将value显式地设置成null,去除entry中value的强引用,帮助GC
            tab[staleSlot].value = null;
            //把当前所在位置的entry数组设置为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的entry进行处理,将value设置为null,清除value的强引用
                    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;
        }
        
        //ThreadLocal 被弱引用包裹,当发生GC时,threadlocal将自动被清楚
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
	}
}

你可能感兴趣的:(java基础,多线程与并发,java,jvm,threadlocal)