系列文章目录
线程安全(一)java对象头分析以及锁状态
线程安全(二)java中的CAS机制
线程安全(三)实现方法sychronized与ReentrantLock(阻塞同步)
线程安全(四)Java内存模型与volatile关键字
线程安全(五)线程状态和线程创建
线程安全(六)线程池
线程安全(七)ThreadLocal和java的四种引用
线程安全(八)Semaphore
线程安全(九)CyclicBarrier
线程安全(十)AQS(AbstractQueuedSynchronizer)
0.前言
如果一个对象想要被回收,那么必须满足两个条件
没有任何引用(根可达算法)与GC。
引用的两种种情况
1循环引用 A里有B,B里有A(Spring框架里也要避免循环依赖)
DemoA a=new DemoA();
DemoB b=new DemoB(a);
a = new DemoA(b);
这种情况下,是否会被回收首先要看a和b是否可达,可达的话,只要一个不为null,则a,b都不被回收。且如果强制使对象为null,还有可能造成内存泄漏。
2.依赖 B里有A,b依赖a,这种把a置为null,也不能被回收,可能造成内存泄漏。
DemoA a=new DemoA();
DemoB b=new DemoB(a);
1.JAVA四种引用类型
1.1.强引用
处于可达状态,JVM不会回收,没有任何引用指向时,就被回收了,是内存泄漏的主要原因
1.2.软引用
SoftReference,垃圾回收机制运行,不会回收,内存一旦不够,就回收(-Xmx),常用于缓存
1.3.弱引用
WeakReference,垃圾回收机制一运行,就回收,
弱引用解决什么问题,就是单依赖
DemoA a=new DemoA();
WeakReference b=new WeakReference(a);
a=null;
DemoA只是被弱引用依赖,即弱可达,也就是说当前指向它的全都是弱引用,这时垃圾收集器会清除所有指向该对象的弱引用,这个弱可达对象标记回收。ThreadLocal中使用,后边详细说明。
1.4.虚引用
PhantomeReference,必须和引用队列联合使用。主要作用跟踪对象被垃圾回收的状态。
get不到,管理堆外内存,当对象被回收时,通过Queue可以检测到,然后清理堆外内存,nio
2.ThreadLocal
2.1.使用场景
ThreadLoca中填充的数据只属于当前线程,对其他线程是隔离的。
用途举例:
Spring的使用ThreadLoca的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接。
TransactionSynchronizationManager
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
2.2.使用方式
ThreadLocal<String> String= new ThreadLocal();
localName.set(“111");
String str = localName.get();
localName.remove();
一般会在Filter中,初始化ThreadLocal信息,加载一些需要在一起请求中到需要的变量,在中间使用是就可以直接使用了,场景:用户权限信息,流程状态信息等
2.3.源码解析
ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
// 获取ThreadLocalMap
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
// 已有,ThreadLocalMap.set
map.set(this, value);
} else {
// 没有,ThreadLocalMap .create
this.createMap(t, value);
}
}
每个线程Thread都维护了自己的threadLocals变量,数据实际存储在线程Thread的threadLocals变量里面。
ThreadLocalMap的初始化方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 初始化
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);
}
ThreadLocalMap getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap的构造方法
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
·
Entry(ThreadLocal<?> k, Object v) {
// 会调用父类构造方法ThreadLocal<?> k直接作为弱引用
super(k);
value = v;
}
}
ThreadLocalMap底层实际和HashMap很像,都有Entry[] table,区别是,HashMap有数组下还有链表或红黑树,它没有。他只会占据table中的一个位置。不会出现两个占据同一位置的可能
ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 计算table的位置
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;
}
// 赋值
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
2.4.总结
在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。实例在堆,但可见性只有线程可见
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,
如果ThreadLocal没有被外部强引用,GC时,ThreadLocal必被回收(因为是弱引用),ThreadLocalMap中的的一个key为null的Entry,而这个Entry无法被访问,线程如果一直使用,就会存在一条强引用链。
Thread Ref - > Thread -> ThreadLocalMap - > Entry -> value 永远无法回收而造成内存泄漏
调用get或者set方法会将key为null的这些Entry都删除,防止内存泄露。但是不一定使用,
将ThreadLocal变量定义成private static的,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
每次使用完ThreadLocal,都调用它的remove()方法,清除数据
问题:为什么ThreadLocal用弱引用,内存溢出?
1.如果强引用,即使TL=null,但key依然指向ThreadLocal,所以内存会泄露,虚引用则不会
2.仍存在内存泄漏,ThreadLocal被回收,key值变为null,则导致value再也不会被访问到