前几年在分析Android消息机制源码时,就碰到了ThreadLocal,但是当时就只引用了《Android开发艺术探索》中结论,没有深入细致地研究它的使用和细节。作为Android开发者而言,日常开发中应该很少使用到ThreadLocal类本身,应该是Java后台开发兄弟会用的多一点。但是,理解了ThreadLocal,可以加深对于Looper的理解。
对于ThreadLocal,日常开发中一般有两种使用场景:
下面我们就针对这两种使用场景举例说明ThreadLocal的使用:
对于拿到时间戳,我们通常需要通过SimpleDateFormat类来将其转换成相应的日期格式,假设我们有如下一个工具类:
public class DateUtils {
public static String format(long milliSeconds) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return dateFormat.format(new Date(milliSeconds));
}
}
现在我们通过线程池来模拟多线程环境:
public class ThreadLocalTest2 {
private static ExecutorService threadPool = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.submit(() -> {
String result = DateUtils.format(finalI * 1000);
System.out.println(result);
});
}
threadPool.shutdown();
}
}
运行后的输出结果如下:
1970-01-01 08:00:03
1970-01-01 08:00:00
1970-01-01 08:00:02
1970-01-01 08:00:04
1970-01-01 08:00:01
1970-01-01 08:00:05
1970-01-01 08:00:08
1970-01-01 08:00:06
1970-01-01 08:00:09
1970-01-01 08:00:07
现在一切都是正常的,但是由于每次调用format方法都是创建一个新的SimpleDateFormat对象,这样是没有必要的。我们可以有如下修改:
public class DateUtils {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static String format(long milliSeconds) {
return dateFormat.format(new Date(milliSeconds));
}
}
现在再运行代码:
1970-01-01 08:00:02
1970-01-01 08:00:02
1970-01-01 08:00:02
1970-01-01 08:00:02
1970-01-01 08:00:07
1970-01-01 08:00:02
1970-01-01 08:00:09
1970-01-01 08:00:09
1970-01-01 08:00:07
1970-01-01 08:00:07
从结果来看,明显这种写法已经出问题了。那么该怎么去解决这个问题呢?接下来,就轮到我们今天的主人公ThreadLocal登场啦!
class DateUtils {
private static ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
public static String format(long milliSeconds) {
return threadLocal.get().format(new Date(milliSeconds));
}
}
现在再运行:
1970-01-01 08:00:00
1970-01-01 08:00:01
1970-01-01 08:00:03
1970-01-01 08:00:05
1970-01-01 08:00:06
1970-01-01 08:00:04
1970-01-01 08:00:09
1970-01-01 08:00:02
1970-01-01 08:00:07
1970-01-01 08:00:08
这样,每个线程之间就互不干扰啦,因为每个进入format()方法的线程所使用的的SimpleDateFormat对象都是线程独享的,相互之间互不干扰的。
假定我们有一个UserInfo类,用来表示用户的信息:
class UserInfo {
int id;
public UserInfo(int id) {
this.id = id;
}
}
再有一个UseInfoHolder类,持有ThreadLocal对象:
class UserInfoHolder {
static final ThreadLocal<UserInfo> holder = new ThreadLocal<>();
}
构造三个Service,分别表示处理逻辑:
class Service1 {
public void process() {
UserInfo userInfo = new UserInfo(1);
UserInfoHolder.holder.set(userInfo);
new Service2().process();
}
}
class Service2 {
public void process() {
System.out.println("in Service2 : " + UserInfoHolder.holder.get().id);
new Service3().process();
}
}
class Service3 {
public void process() {
System.out.println("in Service3 : " + UserInfoHolder.holder.get().id);
}
}
在Service1中,我们为UserInfoHolder中的ThreadLocal设置了值;在Service2、Service3中,我们可以直接通过UserInfoHolder中的ThreadLocal获取设置的UserInfo对象,从而做到共享。
最后写上main测试方法:
public class ThreadLocalTest3 {
public static void main(String[] args) {
new Service1().process();
}
}
运行结果如下:
in Service2 : 1
in Service3 : 1
对于第一种,我们一般会在创建ThreadLocal对象时,直接给定了线程间独享的对象;对于第二种,我们仅仅创建了ThreadLocal对象,是后面通过set方法设置进去的。或者说,我们可以通过这两种方式来给ThreadLocal设置值。
我们就第二种使用方式为例,入手ThreadLocal类的分析。先看ThreadLocal类的构造器:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
啥都没做,那就看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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
总结一下这里面的逻辑:
那么理所当然,我们接下来的分析重点就落到了ThreadLocalMap类。我们从ThreadLocalMap的set方法开始:
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();
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();
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们首先注意一点,ThreadLocalMap的set方法传入的两个参数分别是谁:key是ThreadLocal,value是往ThreadLocal中set的值。也就是说,形成了ThreadLocal对象到往ThreadLocal中set的值两者之间的映射。这个地方的检索我们会发现和我们常见的HashMap有所不同。总之,我们目前可以得到的信息是:ThreadLocalMap中存储着ThreadLocal到放入其中value的映射,并且ThreadLocalMap是存放在Thread类中。
我们可以用下面的图来表示Thread、ThreadLocal以及ThreadLocalMap之间的关系:
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;
}
protected T initialValue() {
return null;
}
可以看出,setInitialValue的实现几乎和set()方法一模一样。ThreadLocal类中的initialValue()方法的默认实现是直接返回null。这个时候我们可以看下第一种使用方式的ThreadLocal.withInitial()的实现:
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
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();
}
}
这样,对于实现就很清晰了。两种使用方式也都联系起来了。
我们再来看ThreadLocalMap的结构:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
private int size = 0;
}
也就是说,ThreadLocalMap底层会维护一个Entry数组,而Entry本身却是WeakReference的子类,并且在构造器中将ThreadLocal传给了父类WeakReference。也就是说,Entry对于ThreadLocal持有的引用是弱引用,它并不会影响GC对于ThreadLocal对象的回收。但是对于value,依旧是强应用,如果不及时清理释放,是会导致内存泄漏的。所以,我们在不使用时,最好调用ThreadLocal的remove方法:
public class ThreadLocal{
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 查找以key为键Entry对象
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
// 这里的clear()方法实际上Reference中提供的方法
e.clear();
expungeStaleEntry(i);
return;
}
}
}
}
public abstract class Reference<T>{
public void clear() {
this.referent = null;
}
}
然后在expungeStaleEntry()方法里:进行了各种置null操作。
而replaceStaleEntry方法里会有这样一行代码:
也就是说,在每次调用set方法的时候也会去做相应防止内存泄漏的检查。
最后,分享一下Spring源码中一处对于ThreadLocal的规范使用实例:
在finally代码块中进行了remove操作。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到,Looper对象实际上是通过ThreadLocal来进行存取的,其真实存放在Thread对象中ThreadLocalMap中,这样再回过头来理解消息机制,印象会更加深刻。
这里给大家推荐一篇大佬的文章,对于ThreadLocalMap底层的实现算法做了很详细的注释:https://www.cnblogs.com/micrari/p/6790229.html