说起ThreadLcoal这个清楚又陌生的词,由于我在公司里也没有什么高并发的场景,只知奇然而不知其所以然,并受到了打击,像我这种天天除了curd其他什么都不会的人还是想了解一下这个ThreadLocal具体到底是干嘛的,并且具体目的还是想自己总结点东西想与别人吹吹牛逼,仅此而已。虽然这只是java里的皮毛。
好了正式开始.
ThreadLocal我也不想去背书,因为我也记不住,那他具体是怎么回事呢?
首先我们先看看这个对象是咋new出来的
ThreadLocal threadlocal = new ThreadLocal;
官方其实已经解释非常清晰明了了,就是维护当前线程内的局部变量,为什么这么说呢,看源码就能知道其所以然
//这是ThreadLocal内部的set方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap,由此可知Thread内部竟然还维护了一个ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
//判断当前对象是否为null
if (map != null) {
//如果不为null那么就把当前对象作为key,值作为value扔到这个map里
map.set(this, value);
} else {
//
createMap(t, value);
}
}
//这个对象其实就是获取当前线程内部的ThreadLocalMapMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//如果当前线程内部的ThreadLocalMap为null 那么就自己初始化一个
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
丛上面的代码中我们可以看到,Thread 里面竟然还维护一个ThreadLocalMap,那么这个破玩应到底是干嘛的,它其实是ThreadLocal的一个静态内部类,他的作用简单的来说就是用来缓存当前线程的一个大map(首次会初始化当前线程的ThreadLodalMap,下次直接从当前线程中获取),重点就在这里看下面的源码
注意这是ThreadLocal内存泄露的原因
//重点来了哇,我靠存储一个弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
//对比弱引用这竟然是个强引用
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
上面是什么鬼为啥有强有弱,跟据java虚拟机的情况我们知道在java虚拟机里,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。而对于强引用,如果一个对象具有强引用,那垃圾回收器绝不会回收它.
我靠重点又来了,我的ThreadLocal还在用,我还在往里放值呢,那么当我java虚拟机的内存不够用了,即使你把key回收了,那我的value还在,岂不是要oom(内存泄露了),
那么问题有来了呀,为甚么jdk元老还要这么设计
key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除。
比较以上两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。
原来如此豁然开朗,牛逼吹的可以
我靠,我又有疑问了,线程过多的话,那他是怎么保证这个map的hash不会冲突呢,你哪里来的这么多问题?因为我的疑问很多哇
那么我们看看下面的源码
这是ThreadLocalMap的源码
//下一个索引
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 上一个索引.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
卧槽这是什么鬼,
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
副本是怎么回事,这个入口在哪里呢,丛ThreadLocal内部的静态类ThreadLocalMap构造函数入手吧
//这是一个公共构造,就是上面默认创建的
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//这是一个私有构造根据官网解析,这个只供ThreadLocal内部createInheritedMap方法调用
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
ThreadLocal内部方法(忽略inheritableThreadLocals调用现成的Map创建方式而已)
丛官网上看只供Thread调用,卧槽绕来绕去又回到了Thread
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
到这里又恍然大悟,原来Thread里竟然维护了一个ThreadLocalMap,估计晚上不用睡觉了,那么让我们来看看Thread源码
//第一次调用
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
//第二次调用注意最后一个参数inheritThreadLocals=true
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
//第三次调用 只看中文
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//维护父集的Map 值
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
this.tid = nextThreadID();
}
原来这是一切的入口,那么接下来我们抛弃以前所有的东西,继续往下看ThreadLocalMap里的私有构造
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
丛上面我们可以看出
1、Thread 拥有属于自己的一个map,key为 ThreadLocal,value为值
2、ThreadLocal 获取值时实际上是从当前 Thread 的map中获取(以自己为key)
这也就是为什么 ThreadLocal 能在每个 Thread 中保持一个副本,实际上数据是放在 Thread 中的。
取值没有说附上源码,今天实在熬不动了,改天再说
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];
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();
if (k == key)
return e;
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
tab[staleSlot].value = 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) {
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了解一点点了,
那么简单的概述一下就是在Tread创建的时候,ThreadLocalMap其实就已经存在了,只不过是为了不改变原来的基础上复制出来一份,放到ThreadLocal里面,作为副本便于我们操作,而真正的值却没有改变!
使用场景
比如数据库连接,或日期转化SimpleDateFormate
这篇文章很好可以看一下
https://www.cnblogs.com/zz-ksw/p/12684877.html
大家如果在看完以后,我如果哪里搞错了,可以在评论区里探讨,明天继续攻克java的基础知识点.