一、ThreadLocal的实现
ThreadLocal源码:
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
}
上面的代码先不用看,跟着分析的思路走,核心的一些东西都会解释到的,先看下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);
}
可以看出,我们需要的线程变量被维护在一个ThreadLocalMap对象里。而ThreadLocalMap维护在Thread里面。
下面看下ThreadLocalMap这个类,这是ThreadLocal的静态内部类,应该是希望不让ThreadLocalMap与其他不相关的类产生关系,所以使用静态内部类的方式来定义。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
private Entry getEntry(ThreadLocal<?> key) {
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 Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
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];
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();
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
}
ThreadLocalMap使用Entry保存值,看下Entry结构
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//实际上真正弱引用的是ThreadLocal对象
super(k);
value = v;
}
}
Entry继承了WeakReference。
看下ThreadLocalMap的构造方法:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建一个容量为16的Entry数组
table = new Entry[INITIAL_CAPACITY];
//计算第一个Entry在数组中的索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//初始化第一个Entry
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置扩容的阈值(负载因子是2/3)
setThreshold(INITIAL_CAPACITY);
}
ThreadLocalMap维护了一个Entry数组。
下面看下ThreadLocalMap如何进行set操作
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通过ThreadLocal的hashcode计算存放的数组的索引位置
int i = key.threadLocalHashCode & (len-1);
// 遍历当前entry数组,看key对应的Entry是否已经存在,如果存在就覆盖掉老的值
for (Entry e = tab[i]; //获取一个Entry
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取当前entry保存的ThreadLocal弱引用对象
ThreadLocal<?> k = e.get();
//如果当前的key存在,则覆盖原来的value
if (k == key) {
e.value = value;
return;
}
//如果key为null,替换这个废弃的Entry。(一般情况就是由于弱引用被垃圾回收机制回收了)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果不存在key对应的Entry,则新生成一个Entry,并放到Entry[]中
tab[i] = new Entry(key, value);
int sz = ++size;
//如果Entry[]中,存在Entry的ThreadLocal引用为null(一般是被被gc回收)的Entry,避免内存泄漏
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
二、Thread类对ThreadLocal的支持
Thread类维护了一个threadLocals,threadLocals为当前线程维护一个ThreadLocal.ThreadLocalMap,使用这种方式,每个线程类都会有自己的一个threadLocals,这个threadLocals维护了当前线程的所有ThreadLocal。
ThreadLocal.ThreadLocalMap threadLocals = null;
三、ThreadLocal机制的内存机制
内存泄漏的原因
从上面的图中可以看到,相关GC ROOT一共有三个,其中比较值得关注的是ThreadRef这个GC ROOT,因为这个引用的生命周期依赖于线程的生命周期。
正常情况下,线程结束后,Thread对象,ThreadLocalMap对象,以及ThreadLocalMap对象中的Entry对象才会被jvm回收(当线程退出时,Thread类执行清理操作,详见Thread类的exit方法),但是如果这个线程不结束呢?那么Entry对象将永远不会被回收(典型的情况就是使用线程数固定的线程池)。
如何解决内存泄漏
Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。
所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
最直接的解决方法,在使用后及时调用ThreadLocal的remove()方法,这个方法会移除Entry数组中ThreadLocal对象对应的Entry对象,避免出现Entry(null->T对象)的情况。
内存泄漏实例
以最常见的tomcat + springMVC环境为例:
@RequestMapping(value = "/thread/local")
public String foo() {
ThreadLocal local = new ThreadLocal(); //1
local.set("foo") //2
}
以上场景就满足了内存泄露的所有条件:
local变量是局部变量,方法结束后对ThreadLocal的强引用消失。下次GC后对ThreadLocal的弱引用也消失、
tomcat使用线程池,每次请求取出一个线程,用完之后放回线程池(意味着线程不会结束)
附:SimpleDateFormat结合ThreadLocal实现线程安全(节选自其他文章,如使用最好先做下自测)
//SimpleDateFormat缓存
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap =
new HashMap<String, ThreadLocal<SimpleDateFormat>>();
//对象锁
private static ReentrantLock sdfLock = new ReentrantLock();
/**
* @Description: 推荐直接使用该方法 获取 DateFormat对象
* 推荐理由:SimpleDateFormat非线程安全且生成开销大
* @param pattern 格式规则
* @return DateFormat
*/
public static SimpleDateFormat getDateFormat(final String pattern) {
ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
if (tl == null) {
try {
//最多10毫秒
if (!sdfLock.tryLock(10, TimeUnit.MILLISECONDS)) {
return new SimpleDateFormat(pattern);
}
tl = sdfMap.get(pattern);
if (tl == null) {
tl = new ThreadLocal<SimpleDateFormat>() {
//这里重写initialValue,第一次get就获取该初始化,省去了set操作
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
sdfMap.put(pattern, tl);
}
} catch (Exception exception) {
log.error(exception.getMessage());
} finally {
sdfLock.unlock();
}
}
return tl.get();
}
注意:每个线程都会有一个SimpleDateFormat对象,如果线程池中固定线程的数量很多,那么SimpleDateFormat对象数量也会很多,可能造成内存泄漏,如果感觉不爽的话,用java8的LocalDate(LocalDateTime)
ThreadLocal设置为static
ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
下面是ThreadLocal类中给出的一个示例。
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
个人认为这种方式,延长了ThreadLocal的生命周期,保证了ThreadLocal对象不会由于没有强引用而被回收,产生ThreadLocal.ThreadLocalMap的key为null的情况。
引用:
四、一个ThreadLocal内存泄漏的例子
public class ThreadLocalTest {
static class BigObject {
private byte[] data;
public BigObject() {
// 100M
this.data = new byte[100 << 20];
}
}
public static void main(String[] args) throws InterruptedException {
foobar();
try {
new BigObject();
} catch (OutOfMemoryError e) {
// 预计抛出次异常,线程睡眠供外部探查内存使用情况
e.printStackTrace();
Thread.sleep(10000000L);
}
}
private static void foobar() {
new ThreadLocal<BigObject>().set(new BigObject());
}
}
foobar()中直接new了一个ThreadMap,并没有对ThreadMap进行强引用,那么在GC时,这个ThreadMap将被回收,此时Entry的key为null,发生了内存泄漏
参考
- https://juejin.im/entry/599a918f6fb9a0249f6a1f52#5threadlocal%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E5%AE%9E%E4%BE%8B
- https://www.cnblogs.com/onlywujun/p/3524675.html
- http://ifeve.com/%E4%BD%BF%E7%94%A8threadlocal%E4%B8%8D%E5%BD%93%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/