ThreadLocal的remove()方法源码分析

1. ThreadLocal

ThreadLocal通俗理解就是线程的私有变量,用于保证当前线程对其修改和读取,如果本线程没有调用set方法设置值的话或者其他线程如果读取的话或者此变量被remove,都会返回null值。

	protected T initialValue() {
        return null;
    }

2. remove方法

remove方法主要是为了防止内存溢出和内存泄露,使用的时机一般是在线程运行结束之后使用,也就是run()方法结束之后。

百度百科:
内存泄露(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了,系统会提示内存溢出。

简单来说,内存泄露的话就是创建了太多的ThreadLocal变量,然后呢,又没有及时的释放内存,内存溢出的话可以理解为创建了多个ThreadLocal变量,然后又给她们分配了占用内存比较大的对象给线程,这样的话就会出现内存溢出。

所以需要记住使用remove方法来清除这些变量。

	public void remove() {
		// 获取ThreadLocalMap对象,这个对象在ThreadLocal中是一个静态内部类
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 如果存在的话,调用方法remove,看②
         if (m != null)
             m.remove(this);
     }


remove()

		/**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;// 获取长度
            // 通过key的hash值找到当前key的位置
            int i = key.threadLocalHashCode & (len-1);
            // 遍历,直到找到Entry中key为当前对象key的那个元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear(); // 清楚对象的引用,帮助GC
                    expungeStaleEntry(i); // 去除陈旧的对象键值对(相当于帮派清理门户,就是将没用的东西清理出去)
                    return;
                }
            }
        }

	public void clear() {
        this.referent = null; // 单纯的将引用指向null
    }

④ 看这个方法之前,需要明确size是什么,size是键值对的个数

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

	private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null; // 当前的值置为null,key的话,在刚刚之前clear已经清除了
            tab[staleSlot] = null; // 将整个键值对清除
            size--; // 数量减一

            // Rehash until we encounter null 直到遇到null,然后rehash操作
            Entry e;
            int i;
            // 从当前的staleSlot后面的位置开始,直到遇到null为止
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                 // 获取键对象,也就是map中的key对象
                ThreadLocal<?> k = e.get();
                // 如果为null,跟上面情况一样,直接清除值和整个entry,数量减一
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else { // 如果不为null,表名当前key存在
                	// 此时就是再哈希操作了
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) { // 如果不等的话,表明与之前的hash值不同这个元素需要重新存储
                        tab[i] = null; // 将这个地方设置为null

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null) // 从当前h位置找一个为null的地方将当前元素放下
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i; // 返回的是第一个entry为null的下标
        }

3. 小结

主要是ThreadLoca的remove方法,使用remove也有这方面原因:在一个操作系统中,线程和进程是有数量限制的。在操作系统上,确定线程和进程唯一性的唯一条件就是线程或者进程Id。操作系统在回收线程或者进程的时候,不是一定要杀死线程或者进程,在繁忙的时候,只会执行清空线程或者进程栈中的数据操作,重复使用线程或进程。如果不使用remove方法的话,id得不到清楚,当下一个线程进入的时候,没有set,依旧可以获取到存在的id值。

发布了74 篇原创文章 · 获赞 12 · 访问量 8197

猜你喜欢

转载自blog.csdn.net/cao1315020626/article/details/103788601