线程封闭是指为避免多线程间的数据共享和同步, 而仅在单线程内进行的数据访问方式.
通常有三种策略:
1. Ad-hoc线程封闭
一种靠开发人员编程保证线程安全的开发方式, 几乎没人使用.
2. 栈封闭
利用局部变量不被多个线程所共享的特性, 避免多线程的并发问题. 关于栈结构可以阅读JVM栈.
所以尽量使用局部变量代替全局变量.
3. ThreadLocal类
将创建的变量与线程关联到一起去, 用空间换时间的方式, 解决线程共享数据的问题.
ThreadLocal
ThreadLocal主要由线程, ThreadLocal, 变量三方面配合使用, 下面简单说下三者之间的关系.
线程和ThreadLocal是1对多的关系, 每个ThreadLocal会保存在线程中的ThreadLocalMap(Map结构)中.
参照ThreadLocal源码, 在保存变量时, 会先根据当前线程取ThreadLocalMap变量.
代码语言:javascript复制ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal与变量是1对1的关系, 两种是保存在ThreadLocalMap中的Entry中. 其中ThreadLocal是Entry.key; 变量是Entry.value;
参看ThreadLocalMap, 保存变量set()方法
代码语言:javascript复制static class ThreadLocalMap {
private Entry[] table;
private void set(ThreadLocal<?> key, Object value) {
...
tab[i] = new Entry(key, value);
...
}
}
在JVM内存中各部分关系如下:
注意Entry.key指向ThreadLocal变量是使用的虚线. 是因为Entry.key是采用的弱引用.
代码语言:javascript复制static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
内存泄漏
弱引用对象, 在没有外部对象引用时, 会被回收掉.
在方法执行结束, 而ThreadLocal没有调用remove()方法释放Entry时, 会因为ThreadLocal变量因为是弱引用被回收掉, Entry.key会指向null, 而Entry和Entry.value还是会指向原对象, 并不会被回收掉.
这在线程执行时间很长或者是线程池中的线程重复使用时, 会无法正确回收对象导致内存泄漏.
内存泄漏优化
为避免内存泄漏的问题, ThreadLocal中也进行了优化.
针对Entry.key是null的情况进行了替换处理, 减少了内存泄漏的风险.
代码语言:javascript复制private void set(ThreadLocal<?> key, Object value) {
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
最好的方式还是在finally中回收掉数据
代码语言:javascript复制ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
try{
threadLocal.set(1);
// ...
} finally {
threadLocal.remove();
}