ThreadLocal是线程局部变量,同一份变量在每一个线程中都保存一份副本,彼此线程之间操作互不影响
测试ThreadLocal
package com.alibaba.fescar.core.protocol.test;
public class TestThreadLocal {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(100);
new Thread(()->{
threadLocal.set(200);
System.out.println("子线程"+threadLocal.get());
}).start();
System.out.println("主线程" + threadLocal.get());
}
}
输出结果
如果子线程不设置ThreadLocal的值,那么get出来的是个什么值呢,将是个null值
ThreadLocal机制
实际上,真正起作用的是ThreadLocalMap,介绍下这个ThreadLocalMap
- 这个Map的Key是ThreadLocal,vlaue是泛型值
- ThreadLocalMap维护在Thread类中
- ThreadLocalMap的创建是懒加载创建
下面是Thread类源码,可以看到Thread中维护了一个ThreadLocalMap
看下ThreadLocal的set、get、remove
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 不为空,加入当前对象和value
map.set(this, value);
else
// 否则创建ThreadLocalMap 传入当前线程和value
createMap(t, value);
}
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 T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
可以发现set和get方法都是懒加载的,当get或者set的时候才会去创建ThreadLocalMap
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
InheritableThreadLocal是用于线程之间隔离的,但是InheritableThreadLocal可以使得子线程去自动拷贝来自父线程的副本数据 ,可以看到子线程拷贝了父线程的值
package com.alibaba.fescar.core.protocol.test;
public class TestThreadLocal {
private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(100);
new Thread(()->{
System.out.println("子线程"+threadLocal.get());
}).start();
System.out.println("主线程" + threadLocal.get());
}
}
输出结果
ThreadLocal与内存泄露
内存泄漏
如果不会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏 如果泄漏的数据量足够大,可能会引起内存溢出,导致程序异常结束。
四种引用关系(强软弱虚)
强引用: 如 Object object= new Object(); 一个对象具有强引用,就不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止
软引用:在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收对象,用来描述一些还有用但并非必需的对象,使用SoftReference表示
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用WeakReference来表示
虚引用:这个对象被收集器回收时收到一个系统通知,最弱的一中引用关系,不能通过虚引用来取得一个对象实例,用PhantomReference表示
GC判断该对象是否被回收
引用计数: 对象被引用的时候,计数器加1,当计数器为0的时候代表对象可以被回收。
可达性分析: 从GC Roots向下搜索,也就是从根对象往下搜索,经过的地方为引用链,如果对象不在引用链,则代表可被回收。
普遍采用可达性分析方法进行垃圾回收
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,具体实现功能的也是它,可以看到ThreadLocal创建的ThreadLocalMap中的key是一个弱引用ThreadLocal对象
使用ThreadLocal过程中,如果ThreadLocal对象强引用断掉后,只剩弱引用,ThreadLocal对象会被回收
此时ThreadLocalMap中的key会变为null,而value没有被回收(value还是被当前线程强引用,只有当Thread线程退出后,value的强引用链才会断开)
同时又由于ThreadLocalMap是Thread中的成员属性,与Thread对象的生命周期是一样长,如果当前线程一直未被销毁(比如使用了线程池),又没有手动删除对应key,这样就会导致value内存泄漏,简而言之就是 key没了,value还在,value不能被回收就是内存泄漏
ThreadLocal的正确使用方法
- 每次使用完ThreadLocal都调用它的remove()方法清除数据
- 将ThreadLocal变量定义成 private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过 ThreadLocal的弱引用访问到Entry的value值,进而清除掉