面向基础之ThreadLocal

ThreadLocal在平时工作中还是比较常见的类,作为一个解决多线程问题的一个途径,还是值得去学习下的.
常见应用场景包括数据库连接、Session管理等.
它的工作原理,简单的说就是ThreadLocal为每个线程创建一个本地副本变量机制,实现与其他线程的隔离.

源码解析

1.get 方法

get方法流程如图:
在这里插入图片描述

	public T get() {
    
    
		// 获取当前线程 
        Thread t = Thread.currentThread();
        // ThreadLocalMap是存储线程副本的关键点
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
        	// 获取Entry对象, 实际保存的数据在Entry中
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 初始化ThreadLocalMap 对象
        return setInitialValue();
    }
    
    private T setInitialValue() {
    
    
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        	// 创建ThreadLocalMap
            createMap(t, value);
        return value;
    }

2.set方法

set方法直接设置数据,ThreadLocalMap 不存在时进行初始化并设值.

	public void set(T value) {
    
    
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        	// 创建ThreadLocalMap
            createMap(t, value);
    }
    private void set(ThreadLocal<?> key, Object value) {
    
    
            Entry[] tab = table;
            int len = tab.length;
            // 计算tab索引值
            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;
                }
				// 由于key是弱引用,key为null之后要释放对应的value值,防止内存泄漏
                if (k == null) {
    
    
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			// tab中对应的key不存在,存入tab中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

3.remove方法

	public void remove() {
    
    
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
   }
   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();
                    // 清除过期Entry
                    expungeStaleEntry(i);
                    return;
                }
            }
   }  

4.replaceStaleEntry

replaceStaleEntry是删除过期的Entry,并添加新Entry的方法

	private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
    
    
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            int slotToExpunge = staleSlot;
            // 向前查找到第一个过期Entry
            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) {
    
    
                    e.value = value;
					// 交换过期entry位置
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // 如果之前向前查找中未发现过期Entry,则以当前位置进行清除
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
                // 向前查找未发现过期Entry,而此时找到过期Entry,则以当前位置进行清除
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // 清除当前过期关联,将新数据关联到该索引
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
            
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

5.expungeStaleEntry方法

expungeStaleEntry方法执行删除过期Entry操作

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

            // 删除过期Entry
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            // tab实际数据数减1
            size--;

            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
    
    
                ThreadLocal<?> k = e.get();
                // 遇到过期的进行清除
                if (k == null) {
    
    
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
    
    
                	//重新计算hash索引 
                    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;
        }

6.cleanSomeSlots 方法

cleanSomeSlots 还是执行清除过期Entry的操作,这里主要做再次扫描清除Entry操作

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

为何ThreadLocal存在内存泄漏?

ThreadLocal实际维护副本变量是通过ThreadLocalMap类来实现.相关定义源码如下:

 	static class ThreadLocalMap {
    
    
 		// Entry 对key进行了弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
            Object value;

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

在进行GC操作后k会被回收,而此时没有v仍保持引用.在特定情况下大量的v堆积,造成内存泄漏.
为何k不采用强引用?
k对应者ThreadLocal实例,当设置业务中实例=null,准备销毁,而threadLocalMap中的Entry仍强引用实例,
根据GC的可达性分析,仍为可达,造成ThreadLocal实例无法进行回收.
使用弱引用?
虽然存在泄漏内存的可能,但是根据之前源码的get/set/remove都对失效Entry进行了处理,可以极大避免问题出现可能.

猜你喜欢

转载自blog.csdn.net/qq_34789577/article/details/103584993