场景一:每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有 SimpleDateFormat
和Random
)。
public class ThreadLocalDemo1 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int end = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalDemo1().date(end);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds) {
Date date = new Date(1000 * seconds);
SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
return dateFormat.format(date);
}
}
class ThreadSafeFormatter {
// 写法一
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
// 写法二
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
场景二:每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取用户信息,该信息在本线程执行的各方法中保持不变),可以让不同方法直接使用,却不想被多线程共享(因为不同线程获取到的用户信息不一样),避免参数传递的麻烦。
public class ThreadLocalDemo2 {
public static void main(String[] args) {
new Service1().process("");
}
}
class Service1 {
public void process(String name) {
User user = new User("feichaoyu");
UserContextHolder.holder.set(user);
new Service2().process();
}
}
class Service2 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service2拿到用户名:" + user.name);
new Service3().process();
}
}
class Service3 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service3拿到用户名:" + user.name);
UserContextHolder.holder.remove();
}
}
class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
String name;
public User(String name) {
this.name = name;
}
}
可以得出 ThreadLocal
的两大作用:
SimpleDateFormat
,显然用 ThreadLocal
可以节省内存和开销。ThreadLocal
拿到,再也不需要每次都传同样的参数。Threadlocal
使得代码耦合度更低,更优雅。Thread
类中有一个 threadLocals
和一个 inheritableThreadLocals
, 它们都是 ThreadLocalMap
类型的变量 。在默认情况下, 每个线程中的这两个变量都为 null
,只有当前线程第一次调用 ThreadLocal
的 set
或者 get
方法时才会创建它们 。 其实每个线程的本地变量不是存放在 ThreadLocal
实例里面,而是存放在调用线程的 threadLocals
变量里面 。 也就是说 , ThreadLocal
类型的本地变量存放在具体的线程内存空间中 。 ThreadLocal
就是一个工具壳,它通过 set
方法把 value
值放入调用线程的 threadLocals
里面并存放起来 , 当调用线程调用它的 get
方法时,再从当前线程的 threadLocals
变量里面将其拿出来使用 。 如果调用线程一直不终止, 那么这个本地变量会一直存放在调用线程的 threadLocals
变量里面 ,所以当不需要使用本地变量时可以通过调用 ThreadLocal
变量的 remove
方法 ,从当前线程的 threadLocals
里面删除该本地变量 。
get
方法:
public T get() {
// 获取当前线程t
Thread t = Thread.currentThread();
// 获取当前线程t持有的map
ThreadLocalMap map = getMap(t);
// 如果map不为null,返回其键值对中保存的value
if (map != null) {
// this指的是当前ThreadLocal,通过当前ThreadLocal获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果Entry存在,通过Entry获取值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 1.map为空
// 2.map不为空,但是还没有存储当前的ThreadLocalMap对象
// 则执行以下逻辑
return setInitialValue();
}
setInitialValue
方法:
private T setInitialValue() {
// 可以自己设置初值,否则默认为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 如果map不为空,则更新值
if (map != null)
map.set(this, value);
// 否则创建新的map
else
createMap(t, value);
return value;
}
createMap
方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set
方法:
public void set(T value) {
// 获取当前线程t
Thread t = Thread.currentThread();
// 获取当前线程t持有的map
ThreadLocalMap map = getMap(t);
// 如果map不为空,则更新值
if (map != null)
map.set(this, value);
// 否则创建新的map
else
createMap(t, value);
}
remove
方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove(this)
方法:
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)]) {
// 找到要删除的key
if (e.get() == key) {
// 去除key的软引用
e.clear();
//
expungeStaleEntry(i);
return;
}
}
}
expungeStaleEntry
方法:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
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;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
在expungeStaleEntry
方法调用前有这么一行代码e.clear();
,该行代码去除了 key 的软引用,那么在expungeStaleEntry
方法中我们注意到ThreadLocal> k = e.get();
,此时 k
为 null
,符合下面的 if
判断,会将e.value
置为 null
,完成了整个键值对的释放,此时就不会内存泄漏了。
内存泄漏是指,某个对象不再使用,但是占用的内存无法回收。
在 ThreadLocalMap
中有一个 Entry
内部类,它的代码如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在 Entry
中保存了键和值,键使用的是 WeakReference
弱引用(可以自动被 GC 回收),值使用的是强引用(无法自动被 GC 回收)。
正常情况下,当线程停止,保存在 ThreadLocal
中的 value
会被垃圾回收,因为没有任何强引用了。但是如果线程不停止,那么 value
就无法被垃圾回收。
链路:Thread -> ThreadLocalMap -> Entry -> Value
因为 value
和 Thread
之间还存在这个强引用链路,所以导致 value
无法回收,就可能会出现 OOM。
因此阿里规约中指出,在使用完 ThreadLocal
后,应该调用 remove
方法。
原理是在 remove
方法中,会调用 expungeStaleEntry
方法,这个方法的意思是清除老的Entry:
if (k == null) {
e.value = null;
}
我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!