ThreadLocal
为多个线程创建副本(管理一类数据)
ThreadLocal
get
public class ThreadLocal<T> {
// 创建计数
private static AtomicInteger nextHashCode =new AtomicInteger();
// 跨度
private static final int HASH_INCREMENT = 0x61c88647;
// hashcode算法 = 基于计数器的hashcode=>用于计算index=hashcode & (length-1)
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 计算key使用的hashcode
private final int threadLocalHashCode = nextHashCode();
// 线程内的Map实现,key是ThreadLocal的弱引用,
// 实例属类在Thread里面
static class ThreadLocalMap {
//
static class Entry extends WeakReference<ThreadLocal<?>> {
// 增加Object存储T/value
Object value;
// key是ThreadLocal的weakReference
// value是存储的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始大小
private static final int INITIAL_CAPACITY = 16;
// map数组,使用取模计算key,只有数组不是单链表
// 本质是weakReference数组,index是Threadlocal计算
private Entry[] table;
private int size = 0;
// 扩容边界,每次扩容2倍
private int threshold;
// 计算新的扩容边界
private void setThreshold(int len) {threshold = len * 2 / 3;}
}
}
// get
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程内部的map
ThreadLocalMap map = getMap(t);
if (map != null) {
// this是当前ThreadLocal对象,计算index
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化
return setInitialValue();
}
//位于Thread
ThreadLocalMap getMap(Thread t) {
// 线程自己的Map
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
// 计算索引值,threadLocalHashCode是单独计算的
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
// 存在即返回
return e;
else
// 不存在,则清理弱引用的流程
return getEntryAfterMiss(key, i, e);
}
private T setInitialValue() {
// 获取初始值,return null;
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 调用get,不存在Map时则初始化thread的属性
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set-remove
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 清理弱引用
m.remove(this);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 计算索引
int i = key.threadLocalHashCode & (len-1);
// i++遍历
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 2:e!=null && k = key,则update
if (k == key) {
e.value = value;
return;
}
// 3. e!=null & k == null说明是无效的存储,则replace
if (k == null) {
// 替换,并清理
replaceStaleEntry(key, value, i);
return;
}
}
// 1. e==null,则插入
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
hash冲突解决
/**
* Increment i modulo len. 顺延解决
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
声明-定义
class Thread implements Runnable {
// 当前线程
ThreadLocal.ThreadLocalMap threadLocals = null;
// 继承使用的Map
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
ThreadLocalMap再ThreadLocal内定义,在Thread内声明
内存泄漏
// 弱引用直接使用
WeakReference<Object> objWeakReference = new WeakReference<>(new Objec());
// TheadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key = ThreadLocal<?>,是弱引用
value = v; // val = Object, 强引用
}
}
Entry[] tabs; // 强引用
ThreadLocalMap.Entry中使用的key为ThreadLocal的弱引用,而value是强引用。
所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap 中就会出现key为null的Entry<null,value>;
ThreadLocal<String> strLocal = new ThreadLocal<String>();
strlocal.set("thread-1");
strLocal = null;
System.gc(); // 出现内存泄漏
假如我们不做任何措施的话,value永远无法被GC回收,只能在线程结束时销毁,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove() 方法的时候,会清理掉key为null 的记录。使用完ThreadLocal方法后最好手动调用remove()方法
// 防止内存泄漏的范式
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("memory-leak");
} finally {
// 防止内存泄漏,ThreadLocal对象不使用时,调用remove手动清空对应的内存
localName.remove();
localName = null;
}
这里只能调用remove函数;因为get/set会清除key=null的对象,remove是清理当前对象key=this;
所以get/set会校验内存内漏的情况,remove会防止内存泄漏的情况
内存泄漏的场景:
- ThreadLocal的引用已经释放,但是Thread还在运行. => 线程池场景
ThreadLocal对象存活,Thread对象死的场景,不会出现内存泄漏问题,因为map在Thread的heap内被释放。
ThreadLocal引用释放,Thread对象仍然存活的场景(
线程池场景),会出现内存泄漏,Thread.ThreadLocalMap里面存储的ThreadLocal-key不能被访问到。可以调用ThreadLocal#remove手动清理,也可以等待GC清理。
解决:
- 手动清理,ThreadLocal的引用localName可以设置成单例 => 改变key的生存周期,跟进程生命周期相同
- 等待GC自己清理,GC清理
当前线程时清理,(线程池时危险,因为线程池一直运行)
Reference
WeakReference弱引用的作用,GC-root不可达的对象能被GC自动清理。
- StringReference,强引用: 普通的引用,强引用指向的对象不会被GC清理;
- WeakReference, 弱引用: 仅有弱引用指向的对象,只要发生gc就会被回收; => 好像有问题
- SoftReference, 软引用: 仅有软引用指向的对象,只有发生gc且throw-OOM异常时,才会被回收;
- PhantomReference,幻引用: 任何情况get()都是null,被GC能获取通知
Thread
Thread.State
public enum State {
NEW,
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* 竞争monitor
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
* 等待被唤醒,LockSupport在AQS.CLH使用
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
* 等待具体时间(有时间戳)
*/
TIMED_WAITING,
TERMINATED;
}
线程池与ThreadLocal
- 多线程的线程是
复用的,可能内存泄漏(coreSize线程复用); - 创建多线程
异步执行业务逻辑时,该ThreadLocal变量并不能传递到子线程中(maxSize线层退出); - 当解决了第一个问题之后,在使用线程池的时候,Java中有的线程池存在线程
复用的情况,这时候ThreadLocal变量会被复用
线程池复用:
coreSize < queue < maxSize;
- 线程池个数 < coreSize, 新的任务使用新的线程池;
- coreSize < 线程池个数 < maxSize, 新任务入队列
- queue满; 新的任务使用新的线程池
- maxSize < 线程池个数; 异常策略
只有阶段1的任务不会出现线程复用的情况;
解决:
- 编程范式
- 使用
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - github.com/alibaba/tra…;
- github.com/alibaba/tra…
interview
- 线程池和threadlocal一起配合使用有什么问题
- 类似的场景:日志框架中MDC、调用链ID的上下文传递、数据库读写分离组件
- [TransmittableThreadLocal源码]mp.weixin.qq.com/s?src=11&ti…