ThreadLocal一般有两种使用场景:
1)保证访问对象的线程安全
2)每个线程内保存全局变量
保证访问对象的线程安全
通常很多工具类并不是线程安全的(典型有SimpleDateFormat和Random),如果我们需要在多线程环境中使用他们,我们可以将它放到ThreadLocal中,保证线程安全。
public class TestSimpleDateFormat {
@Test
public void test1() throws InterruptedException, ExecutionException {
//存在线程安全问题
SimpleDateFormat format = new SimpleDateFormat("yyMMdd");
Callable<Date> task = () -> format.parse("20201218");//演示线程安全
// Callable<Date> task = () -> FormatThreadLocal.holder.get().parse("20201218");//使用ThreadLocal解决线程安全问题
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
futures.add(pool.submit(task));
}
for (Future<Date> future : futures) {
System.out.println(future.get());
}
pool.shutdown();
}
class FormatThreadLocal{
static ThreadLocal<SimpleDateFormat> holder
= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyMMdd"));
}
每个线程内保存全局变量
每个线程内保证全局变量(例如在电商系统在拦截器中保存用户信息),可以在此次请求的调用链路的任意位置直接调用,避免参数传递的麻烦,更加优雅。
//模拟多服务获取ThreadLocal中的用户信息
public class ThreadLocalNormalUsage10 {
public static void main(String[] args) {
new Thread(() -> {
new Service1().process("超哥");
}).start();
new Thread(() -> {
new Service1().process("王哥");
}).start();
}
}
class Service1{
public void process(String name){
User user = new User(name);
UserContextHolder.holder.set(user);
new Service2().process();
}
}
class Service2{
public void process(){
System.out.println(Thread.currentThread().getName()
+",service2:"+UserContextHolder.holder.get().name);
new Service3().process();
}
}
class Service3{
public void process(){
System.out.println(Thread.currentThread().getName()
+",service3:"+UserContextHolder.holder.get().name);
}
}
class UserContextHolder{
static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User{
String name;
public User(String name) {
this.name = name;
}
}
总结:
这两种场景的ThreadLocal的使用方式也不一样:
第一个是在初始化ThreadLocal的时候就设置里面保存的数据,这种情况适用于对象的初始化由我们控制
第二种情况ThreadLocal中的对象生成的时机不由我们控制,是程序运行过程中动态生成的,使用set进行赋值。
注意
:这里使用两种方法给ThreadLocal赋值:set和withInitial。
initialValue和set方法在虽然在起点和入口不一样,但是本质上最终都会调用map.set()方法放入到线程对应的ThreadLocalMap中。
但是使用set方法设置的值在remove后再尝试get,会返回null;使用initialValue设置的值在remove之后只是回到了initialValue定义的初始值。这是因为get方法中定义的如果在ThreadLocalMap获取不到就调用initialValue重新设置初始化到ThreadLocalMap。
一句话:用initialValue初始化的ThreadLocal在被remove后仍然可以重新获取到初始值,而set初始化的ThreadLocal不行
使用ThreadLocal的好处
1、达到线程安全的目的
2、不需要加锁,提高执行效率(不存在对象共享)
3、更加高效地利用内存、节省开销:相比于每个任务都创建相同的对象,我们可以将共用的对象创建出来后放到ThreadLocal中,节省内存和开销,比如(SimpleDateFormat)
4、免去传参的繁琐:线程可以在执行任务的任何时期直接通过THreadLocal获取数据,再也不需要每次都传递同样的参数。ThreadLocal使得代码耦合度更低,更优雅
ThreadLocal的原理
Thread、ThreadLocal、ThreadLocalMap三者之间的关系:
每个Thread对应一个ThreadLocalMap对象(是Thread中的一个属性),一个ThreadLocalMap对象可以存储多个ThreadLocal变量。ThreadLocalMap对象的key是ThreadLocal对象,value就是我们设置的值。一个线程中可以设置多个ThreadLocal都会放到对应线程的ThreadLocalMap中。
ThreadLocalMap原理
//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 Entry[] table;//存放多个ThreadLocal和对应的值
ThreadLocalMap底层是一个Entry来存放键值对,K是ThreadLocal,V也就是我们设置的值。
他跟hashMap的结构类似,区别在于处理hash冲突上:
ThreadLocalMap采用的是线性探测法,也就是如果发生冲突,就继续找下一个位置,而不是用链表拉链。
重要方法源码分析
set操作原理分析:
//ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//先尝试获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);//如果是第一次设置,那么ThreadLocalMap为空,就为该线程创建一个ThreadLocalMap
}
//ThreadLocal
void createMap(Thread t, T firstValue) {
//创建一个ThreadLocalMap对象赋给当前线程,并初始化一组键值对
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap
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);
}
get操作原理分析:
//ThreadLocalMap
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//尝试获取当前ThreadLocal的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//如果ThreadLocalMap为空或者ThreadLocalMap中没有当前ThreadLocal的值
}
//ThreadLocalMap
private T setInitialValue() {
T value = initialValue();//执行初始化当前ThreadLocal的值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//尝试获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);//如果已经有了就直接放入
else
createMap(t, value);//没有就创建一个ThreadLocalMap
return value;
}
remove操作原理分析:
//ThreadLocalMap
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);//如果当前线程得到ThreadLocalMap对象不为空,就从中删除当前ThreadLocal的这组KV
}