十七 线程 ThreadLocal
2018-11-22 本文已影响0人
BeYearn
- 线程 : 简单认为,线程是系统调度的最小单元,一个进程可以包含多个线程,作为任务的真正运作者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其他线程共享文件描述符、虚拟地址空间等。具体实现中线程还分为内核线程, 用户线程(java的线程实现与虚拟机相关) 自jdk1.2后, 现在的模型是一对一映射到操作系统内核线程
线程的生命周期的几个阶段
图片.png
实现Runnable比继承Thread使用除了避免java不支持多继承, 还能更好的与并发库中Executor之类框架结合使用
- threadlocal
这是java提供的一种保存线程私有信息的机制, 因为其在整个线程生命周期内有效. 所以可以方便的在一个线程关联的不同业务模块之间传递信息, 比如事务ID, Cookies等上下文相关信息
ThreadLocal里类型的变量,其实是放入了当前Thread里。因为每个Thread都有一个threadLocals字段,它是一个map(ThreadLocalMap)。这个map的entry是ThreadLocalMap.Entry,具体的key就是这个ThreadLocal的弱引用WeakReference<ThreadLocal<?>> ,value是Object。 当new或者withInitial(Supplier)一个ThreadLocal时那个map还未初始化, 只有set时才初始化。
往进set时,看该Thread的那个字段是否已经初始化了map,有的话就继续往进放。
private static final ThreadLocal<Integer> TL_INT = ThreadLocal.withInitial(() -> 6);
private static final ThreadLocal<String> TL_STRING = ThreadLocal.withInitial(() -> "Hello, world");
private static final ThreadLocal<String> TL_String_new = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
System.out.println(TL_String_new.get()); //null
TL_INT.set(TL_INT.get() + 1);
TL_String_new.set("aaa");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(TL_INT.get()); //6 如果该线程没设置,就得到初始设置的值
System.out.println(TL_String_new.get());//null
}
});
thread.start();
thread.join();
System.out.println(TL_String_new.get()); //aaa
TL_String_new.remove();
System.out.println(TL_String_new.get()); //null
System.out.println(TL_INT.get()); // 7
TL_INT.set(TL_INT.get() + 1);
System.out.println(TL_INT.get()); //8
TL_INT.remove();
System.out.println(TL_INT.get());// 会重新初始化该value,6
}
其数据存储于线程相关的ThreadLocalMap中, 条目的key使用的是threadlocal对象的弱引用,why:
- key 使用强引用:使用除new或withInitial引用的ThreadLocal的对象被回收了,但是ThreadLocalMap中(的条目entry)还持有ThreadLocal的强引用,如果没有手动删除,此时ThreadLocal对象不会被回收,导致Entry内存泄漏。
- key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal在一次gc后也会被回收,这时候ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(ThreadLocalMap的生命周期跟Thread一样长),好在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];; …) {
//…
if (k == null) {
// 替换废弃条目
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// 扫描并清理发现的废弃条目,并检查容量是否超限
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();// 清理废弃条目,如果仍然超限,则扩容(加倍)
}
故:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
注意一个地方. 通常弱引用都会和引用队列配合清理机制使用, 一般不建议与线程池配合, 因为worker线程往往不会退出的