ThreadLocal的使用场景以及原理源码分析

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值