文章目录
1. ThreadLocalMap
-
Thread类中包含了一个这样的成员变量:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
也就是说,每一个Thread实例都有一个属于自己ThreadLocalMap,这个容器是在ThreadLocal类中定义,我们通过ThreadLocal类来操作这个容器。
-
这个容器也就是我们所说的线程本地存储,对于其他的线程是不可见的
2. 什么是ThreadLocal
- ThreadLocal是一个类,他是一个可以操作ThreadLocalMap的一个工具类。 也就是说,我们用ThreadLocal的实例来操作类型为ThreadLocalMap的Thread.threadlocals
- 在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
3.ThreadLocal的功能
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
4. 例子
public class ThreadLocalTest {
static ThreadLocal<String> tl = new ThreadLocal<>();
static ThreadLocal<String> tl1 = new ThreadLocal<>();
public static void twoThreadLocal(){
new Thread(()->{
tl.set("hello");
System.out.println(tl1.get());
}).start();
}
}
5. ThreadLocal底层实现
5.1 ThreadLocalMap的数据结构
-
ThreadLocalMap其实是一个hash map
-
这个类内部维护了一个Entry数组。每一个Entry实例都是一个键值对: key是一个ThreadLocal对象,值是一个Object对象.
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }
我们通过这个Entry,和ThreadLocal的用法,就能判断:这个容器记录的是我们通过哪一个ThreadLocal实例存进来的数据,到时候该ThreadLocal实例调用get()方法时,也只返回通过这个实例存进来的数据。这也就是为什么上面的例子中tl1 get的是null
5.2 ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread();
//调用getmap,将当前线程作为参数传入,这样就获取到了当前线程ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {//如果这个map不是空的就之间向里添加
map.set(this, value);//将this,也就是当前ThreadLocal实例,和要存入的value一同存入到map中
} else {//如果是空的就创建一个
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
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();
}
这个set非常简单:
-
首先 这个方法调用getmap(),将当前线程作为参数传入,这样就获取到了当前线程ThreadLocalMap
-
在判断当前线程的ThreadLocalMap是否为null
-
如果是null则创建一个;反之将this,也就是当前ThreadLocal实例,和要存入的value一同存入到map中
-
⚠️:观察 set(ThreadLocal<?> key, Object value) 我们可以知道,如果entry列表的某个位置enrty不为空,我们再向这个位置添加元素的话会被替换。
static ThreadLocal<String> tl = new ThreadLocal<>(); public static void setTest(){ tl.set("hello"); tl.set("hello1"); tl.set("hello2"); System.out.println(tl.get());//将只输出hello2 }
这也就是之前说的,通过ThreadLocal来操作线程的本地存储,而这个本地存储就是用ThreadLocalMap来实现的。
5.2 ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//这里的this就是当前调用方法的ThreadLocal实例
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
同样
-
首先,我们调用getMap(),获取到当前线程的ThreadLocalMap
-
之后调用getEntry(this),获取到一个key全是当前ThreadLocal实例的Entry。
5.3 Entry
weakreference的一个典型应用就是在ThreadLocal中。
- Entry继承的是 WeakReference。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);//相当于new WeakReference(K)
value = v;
}
}
-
当我们创建一个Entry对象时,调用了它的构造器,这个构造器里它调用了super,也就是WeakReference的构造器,也就是相当于new WeakReference(K).
-
也就是说:Entry中的key是通过弱引用指向一个ThreadLocal
-
引用“链”:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hfzuo1wr-1597103913155)(/Users/luca/MarkText-img-Support/弱引用在ThreadLocal中的应用.png)]
为什么Entry key 要用弱引用?
如果我们Entry不使用弱引用,就算当我们的tl = null时,这个key也不会被回收。而一些线程,比如跑在服务器上的线程一般都是不会结束的,24 hours a day ,7 days a week,所以这个map是长期存在的,如果这是使用强引用,则这个key也是长期存在的。所以会导致内存泄漏(Memory Leak).
如果使用弱引用,一旦我们把tl的强引用一撤,gc一来,则key就马上会被回收,为null。
为什么Entry的value不用弱引用
我们没有办法将value设置为弱引用,如果设置为弱引用的话,gc一来就被回收了,就没有用了,之所以key可以变为弱引用,是因为它还有另外一个强引用值向它
使用Threadlocal,当里面的对象不用时,务必手动remove
因为我们的value还是强引用,所以就算key指向空了,value还不是空,久而久之还是会出现内存泄漏, 所以### 使用Threadlocal,当里面的对象不使用时,务必remove!!!
ThreadLocal<M> tl = new ThreadLocal();
tl.set(new M());
tl.remove();