深入浅出ThreadLocal源码及Redis替换共享Session登录

1.ThreadLocal

1.1为什么要在登录拦截器中使用ThreadLocal存储用户信息

ThreadLocal是线程独立的,使用ThreadLocal方便在各个线程设置一个存储空间用来存放数据,而不必在每个方法中都显式地传递用户信息参数。 大部分使用场景是将用户信息存储到ThreaLocal。

1.2前置知识(强引用、软引用、弱引用、虚引用)

在这里插入图片描述

1.3代码原理

结合工作原理理解代码。

ThreadLocal:

  • ThreadLocal 类提供了 get()set(T value) 方法,用于获取和设置线程局部变量的值。
  • 当你调用 ThreadLocalget() 方法时,它实际上是从当前线程中获取相应的值。
  • 每个线程都持有一个 ThreadLocalMap 实例,该实例存储了所有 ThreadLocal 变量的副本。

ThreadLocalMap:

  • ThreadLocalMapThreadLocal 的内部静态类,它是一个自定义的哈希表,专门用于存储每个线程的局部变量。
  • ThreadLocalMap 使用 ThreadLocal 作为键,值为具体的线程局部变量。
  • 每个线程对象 (Thread) 内部有一个 ThreadLocal.ThreadLocalMap 类型的字段,用于存储与该线程关联的所有 ThreadLocal 变量。
  • 通过这种方式,每个线程都有自己的一套 ThreadLocal 变量副本,而这些变量对于其他线程是不可见的。

Entry:

  • ThreadLocalMap 中的键值对由 Entry 类表示,Entry 类继承自 WeakReference<ThreadLocal<?>>,键是 ThreadLocal 实例,值是对应的线程局部变量。
  • 使用弱引用的原因是防止内存泄漏:当一个 ThreadLocal 实例不再被使用时,即使它的键仍然存在于 ThreadLocalMap 中,它也可以被垃圾回收器回收。

ThreadLocal创建

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUserDTO(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUserDTO(){
        return tl.get();
    }

    public static void removeUserDTO(){
        tl.remove();
    }
}

t1.set方法(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);
        }
    }

Thread t = Thread.currentThread();当前线程
getMap(t);将当前线程传入

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

threadLocals是一个ThreadLocalMap

   ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap继承了WeakReferenc弱引用

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<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

t1.set方法判断完之后 if (map != null) {
map.set(this, value);

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;
                }
            }
1.4使用ThreadLocal为什么会有内存泄漏?
  • ThreadLocal 实例不再被引用时,弱引用会被回收,此时 Entry 的键会变成 null。但 Entry 的值仍然存在,且没有及时被清理,这就可能导致内存泄漏。

  • 当线程长时间存在时,例如在使用线程池的情况下,线程不会被立即销毁,ThreadLocalMap 也会一直存在。这会导致即使 ThreadLocal 被回收,ThreadLocalMap 中的值也不会被及时清理,导致内存泄漏。

    ThreadLocal.remove() 方法将ThreadLocal中的Entry删除。

!!!重点
ThreadLocalMap 使用弱引用存储 ThreadLocal 键,但它的值(即线程局部变量)是强引用的。这导致即使 ThreadLocal 实例被垃圾回收,ThreadLocalMap 中的条目(Entry)仍然保持对值的强引用,从而导致值不能被自动回收,直到显式调用 remove() 或线程结束。
这就是为什么Thread为什么会存在内存泄漏。

2.共享session实现登录

(验证码 用户信息)
在用户输入电话号码之后,获取短信验证码。后端将生成的验证码保存到session中,之后用户验证可以通过设置的key值对于的value进行对比验证。
将用户登录信息保存到session中,是为在登录拦截器中进行身份验证。如何session中保存了用户的登录信息,则说明已登录。然后将用户想你想保存到ThreadLocal中。

3.使用redis替代共享session方式

(验证码 用户信息存储到redis中数据类型的选择)
(刷新过期时间)
(token存储到浏览器客户端)
因为在现实环境下,会部署多台Tomcat服务器,而在不同的Tomcat服务器之间是不能够互相通信的(session之间不能互相共享)。当切换服务器时,即使已登录,却找不到登录信息,判断未登录。
保存到redis时,验证码使用set存储。
保存用户登录信息到redis,使用hash数据结构(类似java中的map)。

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
使用ThreadLocalRedis的目的是为了实现多线程下的用户信息共享和存储。ThreadLocal是一个线程内部的数据存储类,可以在多线程环境中为每个线程维护一份独立的数据副本,保证数据在不同线程间的隔离性。而Redis是一种内存数据库,可以用来存储和共享多线程环境下的用户信息。 在使用ThreadLocalRedis时,可以按照以下步骤进行操作: 1. 首先,通过ThreadLocal在每个线程中存储用户信息。可以将用户信息以键值对的形式存储在ThreadLocal对象中,比如将用户ID作为键,用户信息作为值。这样每个线程都可以通过ThreadLocal获取自己对应的用户信息,实现了线程内部的数据共享。 2. 同时,将用户信息同步到Redis中。可以将用户ID作为Redis的键,用户信息作为值,将用户信息存储在Redis中。这样可以实现多线程环境下的用户信息的跨线程共享。 3. 当用户信息发生变化时,需要及时更新ThreadLocalRedis中的数据。可以通过监听器或拦截器等方式,在用户登录成功后生成唯一的token,并将token与用户信息存储到Redis中,同时更新ThreadLocal中的用户信息。当用户进行操作时,可以通过拦截器根据Redis中存储的token进行判断用户是否为null。 总结:使用ThreadLocalRedis可以实现多线程环境下的用户信息共享和存储。ThreadLocal用于在每个线程中维护一份独立的用户信息,而Redis用于存储和共享多线程环境下的用户信息。通过不断更新ThreadLocalRedis中的数据,可以确保用户信息的及时更新和同步。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值