阅读此文前,请先阅读WeakReference和WeakHashMap
一、ThreadLocal的基本使用
ThreadLocal是一个对象,这个对象是当前线程绑定的,可以在线程的任何位置任何时刻取出
基本使用方式为:
public class ThreadLocalTest {
ThreadLocal<String> threadLocal=new ThreadLocal<>();
@Test
public void testThreadLocal() throws InterruptedException {
new Thread(){
public void run(){
//在线程的任意地方设置变量
threadLocal.set("你");
method();
}
}.start();
new Thread(){
public void run(){
threadLocal.set("好");
method();
}
}.start();
Thread.sleep(3000);
}
public void method(){
//可以在当前线程的任意地方获取变量
System.out.println(threadLocal.get());
}
}
二、ThreadLocal的原理
首先对类的构成有个理解
image.png
ThreadLocal有一个静态内部类ThreadLocalMap,因为是静态内部类,意味着不依赖于外部类。
ThreadLocalMap内部的静态内部类Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap有点儿像WeakHashMap,但区别是,entry的构造方法
它并没有传入一个referenceQueue,我们知道,当使用reference.null,我们不会对referenceQueue进行enqueue操作。
也就是虽然会把referent置为null,但不和weakHashMap一样,通过queue.poll方法找到陈旧的数据
一步步来分析ThreadLocal的实现
1.set方法threadLocal.set("好");
获得map,我们可以看到调用了getMap方法,最终返回了当前线程的ThreadLocal.ThreadLocalMap,即每个线程调用set方法,都是在当前线程的map中进行操作的,map里会放入Entry<K,V>,对象的key是threadlocal自身
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
...
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2.get方法,threadLocal.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();
}
...
三、ThreadLocal弱引用的用处
image.png我们看看关于ThreadLocal里的相关引用
- 1.ThreadLocalRef是一个指向ThreadLocal对象的强引用
- 2.CurrentThreadRef是一个指向当前线程对象CurrentThread的强引用
- 3.当前线程对象有一个属性ThreadLocalMap,这是一个强引用
- 3.Map是一个Entry[],就是最普通的一维数组,如果碰撞就i+1,如果size超过阈值,就resize数组。map的Entry[i]是指向Entry的强引用
- 4.Entry对象有属性Value是强引用,Entry本身是若引用的子类,referent是ThreadLocal
好了,现在可以分析为何要这样进行设计。
类比于WeakHashMap,它的put方法是put(K,V),可以是完全不相关的东西。
但LocalThread的设计,使得LocalThreadMap的Key其实是当前对象,这意味着什么。
假设一个场景,你主观上想回收你的ThreadLocal
ThreadLocal<String> tl=new ThreadLocal<>();
tl.set("abc");
....
tl=null;
- 若entry是普通的引用,也就是强引用会发什么?
当你的线程CurrentThread还存活着时,CurrentThread.threadLocalMap会存在,当你没有手动删除那个Entry时,map[i]指向的entry也自然会存在,那Entry指向ThreadLocal也会一直存在。这导致ThreadLocal对象无法被回收,且这时由于你已经没有了ThreadLocalRef,你在Map中无法根据key来找到对象,除非你去轮询所有,不然你就再也删除不掉那个Entry了,除非线程死,不然你无法回收那部分内存
当程序中己动态分配的堆内存由于某种原因程序未释放或无法释放时,就发生了内存泄漏(Memory Leak) - 若entry是一个弱引用有什么好处
同样刚刚的例子,你把CurrentThreadRef的强引用置为null,此时ThreadLocal对象只存在弱引用,会在gc的时候作为referent被置为null
四、这种设计下,ThreadLocal还可能会有内存泄露吗?
当ThreadLocalRef置为null后,young GC会把referent置为null,但此时value还存在。
ThreadLocal的expungeStaleEntry方法,会在remove,rehash等方法中调用,如果你不显式调用remove方法,意味着你只有在rehash的时候,才会进行轮询数组,清除无效数据,所以不使用remove时容易造成内存泄露
五、强引用,软引用,弱引用,虚引用的例子
package example.reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
/**
* @author liuhaibo on 2018/03/06
*/
public class WeakRefDemo {
public static void main(String... args) {
// all these objects have a strong reference
Object a = new Object();
Object b = new Object();
Object c = new Object();
// other references to these objects
Object strongA = a;
SoftReference<Object> softB = new SoftReference<>(b);
WeakReference<Object> weakC = new WeakReference<>(c);
// free the former strong references to these objects:
// there is still a strong reference(strongA) to the first object
a = null;
// only a soft reference(softB) refers to the second object
b = null;
// only a weak reference(weakC) refers to the third object
c = null;
System.out.println("Before gc...");
System.out.println(String.format("strongA = %s, softB = %s, weakC = %s", strongA, softB.get(), weakC.get()));
System.out.println("Run GC...");
System.gc();
// object with only soft reference will be cleaned only if memory is not enough: 用来做缓存很不错
// object with only weak reference will be cleaned after a gc operation:
System.out.println("After gc...");
System.out.println(String.format("strongA = %s, softB = %s, weakC = %s", strongA, softB.get(), weakC.get()));
}
}
结果为
Before gc...
strongA = java.lang.Object@3af49f1c, softB = java.lang.Object@19469ea2, weakC = java.lang.Object@13221655
Run GC...
After gc...
strongA = java.lang.Object@3af49f1c, softB = java.lang.Object@19469ea2, weakC = null
网友评论