ThreadLocal应用和原理解析
Posted 泡^泡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal应用和原理解析相关的知识,希望对你有一定的参考价值。
ThreadLocal主要是实现线程隔离,解决线程安全问题。
ThreadLocal的使用
package thread;
public class ThreadLocalDemo
static final ThreadLocal<Integer> local = new ThreadLocal<Integer>()
protected Integer initialValue()
return 0; //初始化一个值
;
public static void main(String[] args)
Thread[] thread = new Thread[5];
for(int i = 0;i < 5; i++)
thread[i] = new Thread(()->
int num = local.get(); //获得的值都是0
local.set(num+5); //设置到local中
local.remove();
System.out.println(Thread.currentThread().getName()+"-"+num);
);
for (int i= 0; i < 5;i++)
thread[i].start();
ThreadLocal案例
package thread;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest
//非线程安全
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal();
private static DateFormat getDateFormat()
//从当前线程的范围内获得一个DateFormat
DateFormat dateFormat = threadLocal.get();
if(dateFormat == null)
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//Thread.currentThread范围内设置一个SimpleDateFormat
threadLocal.set(dateFormat);
return dateFormat;
public static Date parse(String strDate) throws ParseException
return getDateFormat().parse(strDate);
public static void main(String[] args)
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20;i++)
executorService.execute(()->
try
System.out.println(parse("2022-07-09 10:00:00"));
catch (ParseException e)
e.printStackTrace();
);
ThreadLocal的原理
- ThreadLocal能够实现线程的隔离,当前保存的数据,只会存储在当前线程范围内。
主要方法
- set():在当前线程范围内,设置一个值存储到ThreadLocal中,这个值仅对当前线程可见。相当于在当前线程范围内建立了副本。
- get():从当前线程范围内取出set方法设置的值。
- remove():移除当前线程中存储的值。
- withInitial:java8中的初始化方法。
源码分析
public void set(T value)
Thread t = Thread.currentThread();
// 如果当前线程已经初始化了map。
// 如果没有初始化,则进行初始化。
ThreadLocalMap map = getMap(t);
if (map != null) //修改value
map.set(this, value);
else //初始化
createMap(t, value);
createMap
void createMap(Thread t, T firstValue)
//绑定当前线程
t.threadLocals = new ThreadLocalMap(this, firstValue);
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
table = new Entry[INITIAL_CAPACITY]; //默认长度为16的数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //计算数组下标
table[i] = new Entry(firstKey, firstValue); //把key/value存储到i的位置.
size = 1;
setThreshold(INITIAL_CAPACITY);
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();
// i的位置已经存在了值, 就直接替换。
if (k == key)
e.value = value;
return;
//如果key==null,则进行replaceStaleEntry(替换空余的数组)
if (k == null)
replaceStaleEntry(key, value, i);
return;
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
- 把当前的value保存到entry数组中
- 清理无效的key
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;
// Find either the key or trailing null slot of run, whichever
// occurs first
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 we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
- 如果当前值对应的entry数组中key为null,那么该方法会向前查找到还存在key失效的entry,进行清理。
- 通过线性探索的方式,解决hash冲突的问题。
key的清理过程
- 向前有脏Entry向后查找到可覆盖的Entry
- 向前有脏Entry向后未查找到可覆盖的Entry
- 向前没有脏Entry向后找到可覆盖的Entry
- 向前没有脏Entry向后未找到可覆盖的Entry
内存泄漏问题
上面代码中的expungeStaleEntry() 方法是帮助垃圾回收的,根据源码,我们可以发现get 和set 方法都可能触发清理方法 expungeStaleEntry() ,所以正常情况下是不会有内存溢出的 但是如果我们没有调用get 和set 的时候就会可能面临着内存溢出,养成好习惯不再使用的时候调用remove(),加快垃圾回收,避免内存溢出。
退一步说,就算我们没有调用get 和set 和remove 方法,线程结束的时候,也就没有强引用再指向ThreadLocal 中的ThreadLocalMap了,这样ThreadLocalMap 和里面的元素也会被回收掉,但是有一种危险是,如果线程是线程池的, 在线程执行完代码的时候并没有结束,只是归还给线程池,这个时候ThreadLocalMap 和里面的元素是不会回收掉的。
以上是关于ThreadLocal应用和原理解析的主要内容,如果未能解决你的问题,请参考以下文章