概念
ThreadLocal
是并发编程中的一种对象共享方式,从字面意思上看,我们大概能推断出它的用法是作为线程局部变量来使用的。它的好处是,可以存有每个线程独立的数据而互不影响。可以简单的把ThreadLocal
理解为一个类似于HashMap
的数据结构。更准确的说是WeakHashMap
。可以简单来写一个简化版的ThreadLocal
。
class SimpleThreadLocal {
static Map<String, String> map = new ConcurrentHashMap<>();
private static CountDownLatch latch = new CountDownLatch(5);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
int value = i;
new Thread(() -> {
map.put(Thread.currentThread().getName(), Integer.toString(value));
latch.countDown();
}).start();
}
latch.await();
System.out.println(map);
}
}
这就可以被作为一个ThreadLocal
来使用了,在这个map中,key是它的名称,value可以作为每个线程独有的数据来存储。但实际上ThreadLocal
却不是这么的简单,但也复杂不了多少。
深度解析
接下来看一段源码吧
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);
}
上述展示了ThreadLocal
的put
方法源码,看起来基本上是一目了然的,基于我们之上手写的ThreadLocal
来说,不同的点在于,源码的map是线程独有的,而我们的map是共享的。这时候就要问自己一个问题,为什么要这么设计呢?
答案是,如果采用共享的map,当某个线程被销毁的后,它的数据会占用内存不能被回收。这样导致了很多垃圾数据的积累。所以使用单个线程自带map存储数据是相当方便的。
这时候又出现了新的问题,当我们使用线程池的时候,线程池中的核心线程不会被销毁,有时候线程会把较大的对象存入结构中,长时间的积累将导致内存泄漏。
避免的方式是,ThreadLocal
提供了remove()
接口,对于不需要的对象,要及时调用remove()
方法去清理。
再来看下get()
方法的源码
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();
}
get()
的逻辑从表面看来是个相当容易理解的,重点的深入到ThreadLocalMap
中看看getMap的实现。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
上述代码提供了ThreadLocalMap
中的一个方法,我们能发现在其中有一个什么精巧的设计。我们可以把k
当作线程实例,v
当作值。整个Entry
继承了WeakReference<ThreadLocal<?>>
弱引用,弱引用的方式就是在Java虚拟机GC发现的时候,就可以立即回收。这样当外部的ThreadLocal
强引用被回收的时候,这个Entry
的key就会被置为null。
应用与注意事项
我们简单举个例子
private static ThreadLocal<Connection> threadLocal = ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection("url");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
});
可以很好的想到,ThreadLocal
可以做数据库的连接池。或者web端的session
等等。它的作用可以说是十分广泛。
在《Java高并发程序设计》这本书中,有个例子验证了共享一个变量和使用ThreadLocal
对性能的影响,有兴趣可以读一读,条件以及得出的结论是:
条件:
4个线程
一千万的随机数循环
结论:
多线程共享一个Random 耗时:`13s`
使用ThreadLocal 耗时:`1.7s`
另一方面,我们需要注意的是,ThreadLocal
相当于全局变量,如果滥用它将会导致代码的可重用性降低以及增加耦合度,这是格外需要注意的。
网友评论