什么是ThreadLocal
ThreadLocal 是JDK1.2新增的类,作用是为每个线程为维护一个独立变量副本,目的是为了解决数据隔离问题,线程之间的变量互不干扰,解决数据安全问题,如比较常见的场景:为每一个线程保存当前的用户信息,以便于参数传递,又或者jdbc的给与每个线程connection。
ThreadLocal的方法有以下几种
//设置初始化默认值 ,jdk1.8新增的函数方法
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
//设置线程变量副本
public void set(T value)
//获取线程变量副本
public T get()
//移除线程变量副本
public void remove()
使用方式也非常简单
public class TestThreadLocal {
//线程本地存储变量
private static final ThreadLocal<Integer> tl= ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {//启动三个线程
Thread t = new Thread(() -> add10ByThreadLocal());
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
for (int i = 0; i < 5; i++) {
Integer n = tl.get();
n += 1;
tl.set(n);
System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
}
}
}
实现原理
ThreadLocal内部set 方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以看到,threadlocal的set,是先调用getMap方法,而这个方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
实际上是来源于线程的,我们在set的时候,实际操作的是thread里面的threadLocals
而设置进去的threadLocalMap结构,
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
每个线程都有一个threadlocalMap,而每次setvalue时,我们的threadlocal会被当做key,设置到当前线程的map集合中,而每个线程的map是自己的变量副本,那么我们不管是设置还是取出,都是操作的自己线程的数据。
再来看看get
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();
}
可以看到也是操作的是thread里面的threadlocalMap,由此可见,threadlocal本身并不保存数据,结构如下。
内存泄漏的原因
内存泄露的定义:不再使用的对象,但是内存无法回收
而在threadlocal中,存在一种这样的情况
一个thread一直处于运行状态,而threadlocalmap中,已经放置了一个entry,上面我们看到了,entry的key就是threadlocal,而threadlocal是弱引用,随时会被垃圾回收器回收,那么一旦回收,强引用的value对应的key变成了null,而再去使用threadlocal.get() 也无法再获取到数据,成为不可回收对象,发生内存泄漏。
1.这里就有一种解决方案,将threadlocal定义为static,那么threadlocal不会被回收,不会发生内存泄漏问题。
2.调用remove 方法。在使用threadlocal后,手动remove值,避免内存泄漏
强引用与弱引用
强引用,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。