ThreadLocal源码深度解析

一、介绍

ThreadLocal叫做线程本地变量,会给每个线程都分配一个属于自己的本地变量,用于线程变量隔离,保证线程安全
每个线程内部都会有自己的一个ThreadLocalMap,然后key是ThreadLocal,Value是我们自己设定的值,注意Entry中Key是弱引用,弱引用是发现就回收,继承了WeakReference这个,但是V不是,所以这里可能会产生

二、结构

简图
在这里插入图片描述

在这里插入图片描述

Thread.class

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

然后是我们的ThreadLocalMap

static class ThreadLocalMap {
		//因为是map,所以肯定是KV结构
        static class Entry extends WeakReference<java.lang.ThreadLocal<?>> {
           
            Object value;

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

		//初始默认大小
        private static final int INITIAL_CAPACITY = 16;

		//Entry桶
        private ThreadLocalMap.Entry[] table;
		//size数量
        private int size = 0;
		//阈值
        private int threshold; // Default to 0
		//
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

}
重要方法

在ThreadLocal中,有这几个重要的方法,

  • get()
  • set()
  • remove()
  • initialValue()

get()方法

	public T get() {
		//获取到当前线程id
        Thread t = Thread.currentThread();
        //获取到当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果不为空
        if (map != null) {
        	//通过ThreadLocal作为Key来查询对应的值,注意getEntry方法
            ThreadLocalMap.Entry e = map.getEntry(this);
            //如果这个值不是空的
            if (e != null) {
                //强转
                T result = (T)e.value;
                return result;
            }
        }
        //如果map是空的或者获取到的值是空的,需要进行初始化或者设置初始值
        return setInitialValue();
    }

getEntry()

	private ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            ThreadLocalMap.Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
	private ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocalMap.Entry e) {
            ThreadLocalMap.Entry[] tab = table;
            int len = tab.length;
			//这里也就是不断++,访问该处的entry,因为在ThreadLocalMap中出现的hash冲突,是依赖于++解决的,
			//而不是像HashMap那种链地址法
            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                	//这里是我们的i+1
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

setInitialValue()

	private T setInitialValue() {
		//这个是我们需要自己定义的代码
        T value = initialValue();
        //获取到当前线程id
        Thread t = Thread.currentThread();
        //获取到线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //map不为空,只需要赋初值
        if (map != null)
            map.set(this, value);
        //map为空,需要继续宁create来创建一个ThreadLocalMap
        else
            createMap(t, value);
        return value;
    }

createMap()

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

set()方法

	public void set(T value) {
		//获取到线程ID
        Thread t = Thread.currentThread();
        //获取到Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	//调用map.set进行设置
            map.set(this, value);
        else
        	//创建一个线程
            createMap(t, value);
    }

ThreadLocalMap # set()

	private void set(ThreadLocal<?> key, Object value) {
			//获取到我们的Entry桶
            ThreadLocalMap.Entry[] tab = table;
            //获取长度
            int len = tab.length;
            //先获取到我们的索引
            int i = key.threadLocalHashCode & (len-1);

			//这里会有一个nextIndex来记录我们的下一个索引,需要根据这个nextInde来进行循环的下一个位置
			//这里如果计算出的key不相等,就会将i++,然后判断i++处的key是不是空的,这里是利用移位法来解决的hash冲突
			//而不是像hashMap那种链地址法
            //for(entry=tab[index] ; entry!=null ; e=nextIndex)
            for (ThreadLocalMap.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放上去
            tab[i] = new ThreadLocalMap.Entry(key, value);
            //size++
            int sz = ++size;
            //判断是不是需要进行扩容,扩容需要判断size是否大于0.75*threadhold
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
 private void resize() {
            ThreadLocalMap.Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            //构建一个两倍长度的数组
            ThreadLocalMap.Entry[] newTab = new ThreadLocalMap.Entry[newLen];
            int count = 0;
			//遍历
            for (int j = 0; j < oldLen; ++j) {
                ThreadLocalMap.Entry e = oldTab[j];
                //如果下标有值
                if (e != null) {
                	//获取到K
                    ThreadLocal<?> k = e.get();
                    //K是空的,想想弱引用,但上面的Entry却不是空的
                    if (k == null) {
                        e.value = null; // Help the GC
                        
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                        	//计算下标
                            h = nextIndex(h, newLen);
                       	//直接设置
                        newTab[h] = e;
                        count++;
                    }
                }
            }
			
            setThreshold(newLen);
            size = count;
            table = newTab;
        }

remove()

	public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
        	//调用ThreadLocalMap.remove()方法
            m.remove(this);
    }

	private void remove(ThreadLocal<?> key) {
            ThreadLocalMap.Entry[] tab = table;
            int len = tab.length;
            //计算下标
            int i = key.threadLocalHashCode & (len-1);
            //进行遍历
            for (ThreadLocalMap.Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //如果key相等
                if (e.get() == key) {
                	//进行清除
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
	private int expungeStaleEntry(int staleSlot) {
            ThreadLocalMap.Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            ThreadLocalMap.Entry e;
            int i;
            //遍历
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                //如果key是空的,全部置为空
                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;
        }

nextIndex

		private static int nextIndex(int i, int len) {
			//返回i+1
            return ((i + 1 < len) ? i + 1 : 0);
        }

如何防止使用ThreadLocal的时候出现内存泄漏

1、每次使用完ThreadLocal都调用它的remove()方法清除数据
2、将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值