从源码来看ThreadLocal
作者:互联网
从源码来看ThreadLocal
-
为什么threadLocal线程安全
public T get() { // 从下面2行代码可以看出不管是在哪个线程中运行,它拿出的ThreadLocalMap都是当前线程上的threadLocals的对象 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
-
为什么要慎用threadLocal,发生内存泄漏的真正原因
static class ThreadLocalMap { // ThreadLocalMap内的Entry是WeakReference的子类 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
名称 | 描述 | 生命周期 |
---|---|---|
强引用 | 普通的对象都是强引用 | 宁愿OOM也不回收 |
软引用(SoftReference) | 一般用于系统内的高速缓存 | 当内存不足的时候会回收 |
弱引用(WeakReference) | 这个用的很少,threadLocal中的Entry使用到了它 | 当gc的时候会回收,不管内存还够不够 |
虚引用(PhantomReference) | 一般用来跟踪对象的生命周期,基本不会使用到 | 未知 |
-
怎么访问其他线程threadLocal中的数据
Thread.java // 这个变量用来保障线程安全问题 ThreadLocal.ThreadLocalMap threadLocals = null; // 这个变量用来实现父子线程中的threadLocal通讯 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 省略很多代码 if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 从这里看出来父线程中的inheritableThreadLocals会设置到子线程的inheritableThreadLocals上 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); // 省略很多代码 }
-
threadLocal的数据结构
threadLocal的底层是通过一个叫做threadLocalMap的数据结构实现的,但是threadLocalMap并没有实现Map接口,也没有使用链表,它里面用的是一个叫做Entry的数组
static class ThreadLocalMap { // static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; // threadLocal中的数据都存放在table这个数组中 private Entry[] table;
当threadLocalMap发生hash冲突的时候又是怎么解决的
ThreadLocal.ThreadLocalMap private Entry getEntry(ThreadLocal<?> key) { // 用当前threadLocal的hashCode 和储存数组的长度进行& int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // 当存在这个值并且2个key值相等的时候返回找到的值 if (e != null && e.get() == key) return e; else // 当找不到或者发生hash冲突的时候就需要下面这个方法去解决了 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(); if (k == key) return e; if (k == null) // 当key 是null的时候清理掉当前entry上存储的值,避免出现内存泄漏 expungeStaleEntry(i); else // 当前的坐标找不到,那么就继续往下找 // 从这里可以看出当hash冲突严重的时候threadLocal的效率会大幅度下降 i = nextIndex(i, len); e = tab[i]; } return null; }
-
正确使用ThreadLocal的姿势
1. 初始化的时候使用static修饰 private static ThreadLocal<String> th = new ThreadLocal<String>(); 2. 在业务代码中使用ThreadLocal th.set('aaaa'); try{ // 业务代码。。。。 }finally{ // 虽然说每次在set和get的时候threadLocal都会情况key为null的值,但是由于threadLocal的生命周期和thread一样,当使用线程池的情况下,如果我们有一个对象使用用了之后一直没有移除,并且也没有在该线程内再次get set 那么,这个对象占用一块内存空间将一直得不到释放,从而造成内存泄漏 th.remove(); }
标签:来看,ThreadLocalMap,threadLocal,ThreadLocal,源码,线程,Entry,null 来源: https://blog.csdn.net/sssdal19995/article/details/111225207