前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal应用场景及源码分析

ThreadLocal应用场景及源码分析

原创
作者头像
朱季谦
发布2023-11-12 22:44:32
2330
发布2023-11-12 22:44:32
举报

原创/朱季谦

在登录模块里,当用户完成一次登录会话后,往往需要将其登录成功的信息进行缓存。不同的登录会话,属于不同的会话线程,彼此需要互不影响。这就意味着,登录成功的信息,只属于该次会话线程的本地变量,这时,就可以基于ThreadLocal缓存属于该会话线程的用户信息,类似线程的私有本地变量。

可以这样实现,先定义一个存储用户信息的POJO类——

代码语言:javascript
复制
@Data
public class User {
    private String username;
    private String role;
    ......
}

写一个专门缓存会话用户信息的类SessionCsche,在该类里定义一个static修饰的ThreadLocal变量,再定义一个将User信息缓存到ThreadLocal的方法,以及从ThreadLocal取出的方法——

代码语言:javascript
复制
public class SessionCsche {
    //定义一个static修饰的ThreadLocal变量
    private static ThreadLocal<User> threadCache = new ThreadLocal<>();
​
    //缓存用户信息
    public static void put(String username, String role) {
        User user = new User();
        user.setUsername(username);
        user.setRole(role);
        threadCache.set(user);
    }
​
  //读取缓存在该会话线程里的用户信息
    public static String getUsername() {
        User user = threadCache.get();
        if (user == null) {
            return null;
        }
        return user.getUsername();
    }
}

以上,就是基于ThreadLocal实现一个线程隔离缓存变量的场景。除此之外,在DataSource数据源动态切换场景下,也可以基于ThreadLocal实现不同的线程获取到不同的DataSource数据源,进而发起一次数据库访问会话。

以上主要是常见的应用场景,在大概了解一下ThreadLocal的应用后,就可以阅读一下其内部的源码实现类。

先来看threadCache.set(user)内部实现——

代码语言:javascript
复制
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

该源码首先通过 Thread t = Thread.currentThread()获取到当前线程对象,再通过getMap(t)取出当前线程的ThreadLocalMap对象,如果获取到的map不为空,就直接将new ThreadLocal<>()创建的对象threadCache当作key,以需要缓冲的数据当作value,以key-value格式缓存至线程的ThreadLocalMap对象里。若当前线程的map为空,则表示当前线程还没有创建ThreadLocalMap对象,因此就需要通过createMap(t, value)完成ThreadLocalMap对象创建后,再做缓存。

简单看一下createMap(t, value)内部逻辑,可以看到,里面通过new ThreadLocalMap(this, firstValue)创建了一个ThreadLocalMap对象,然后将ThreadLocal对象本身和需要缓存的数据,当作参数传给该ThreadLocalMap的构造方法——

代码语言:javascript
复制
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

进入到 new ThreadLocalMap(this, firstValue)内部,主要逻辑就是创建一个存放hash值到table数组,长度INITIAL_CAPACITY默认是16,这与HashMap默认长度一致都是16,然后通过firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)对传进来的ThreadLocal对象当作key去做一次哈希计算,计算其在数组中的索引位置,然后在该位置创建新的Entry对象,用于存储键值对,这里的键是ThreadLocal对象,值是需要缓存的User用户数据。

代码语言:javascript
复制
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);
}

进入到Entry类,看一下其内部结构——

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
​
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

可见,这里的关键地方,正是这一个当前线程里的ThreadLocalMap对象。

每一个线程都有只属于本线程的ThreadLocalMap对象,类似线程的本地变量,当前线程只能读取到自己的ThreadLocalMap对象缓存信息,使用到了一种用空间换时间的方案。

接着,看一下ThreadLocal读取缓存的原理——

代码语言:javascript
复制
  User user = threadCache.get();

进入到get()内部,首先通过 Thread t = Thread.currentThread()获取当前线程对象,接着去取出当前Thread对象的ThreadLocalMap,若前面已经有数据缓存到ThreadLocalMap了,说明该map就是有值的。这时候,通过ThreadLocalMap.Entry e = map.getEntry(this),以当前ThreadLocal对象当作key,内部以 key.threadLocalHashCode & (table.length - 1)方式计算出该key的在ThreadLocalMap内部数组的哈希索引位置,进而取出该缓存数据赋值给e,如果e != null,说明取出的缓存数据有效,就可以强制转换相应的类型对象,进而返回。

代码语言:javascript
复制
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();
}

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档