ThreadLocal使用起来有点香

543 阅读3分钟

ThreadLocal

ThreadLocal:即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。最后记得使用完调用remove()方法释放,防止内存泄漏。说的再通俗易懂点,就是各个线程之间可以通过ThreadLocal实现线程之间数据的隔离,相互不可见,只有我线程自己能拿到我存在ThreadLocal里面的值。

需求

我现在需要把用户的ID当做key,把用户的姓名存到redis里面,用户的ID是前端传给我的,每个用户的ID不一样,这个案例完全可以不用ThreadLocal做如下:

@Autowired
private  RedisTemplate redisTemplate;
public void testI(String userId) {
       
        //业务逻辑......
        
        //缓存userId和userName
        setValue(userId,"雷小鸿");

    }
    public  void setValue(String key,String value){
        redisTemplate.opsForValue().set(key, value);
    }

需求拆分封装

我现在需要把对redis的操作封装成一个类,里面有两个方法,获取redis的key,和存userName的方法那么我如下这样写

    /***
     * 拿key为userId的key
     */
    public static String getKey() {
        String key = "USERID::"+ "userId";
        return key;
    }
    /***
     * 存redis
     */
    public  void setRedisValue(String value) {
        //存key userId
        String key = getKey();
        redisTemplate.opsForValue().set(key, value);

    }

发现一个问题getKey这个方法我需要返回USERID::和userId但是这个userId是前端传给我的不能固定写成userId,那么我怎么写呢难道这样写getKey(String userId) 把用户userId用参数的形式传过来吗,如果不传我怎么搞,这时候ThreadLocal就可以使用上了。

封装ThreadLocal类

/**
 * 功能简述:ThreadLocal功能
 *
 * @author leixiaohong
 * @date 6/20/21 12:06 AM
 */
public class UserHelper {
    private static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();

    private static final String USER_ID = "user_id";

    /***
     * @Description: 往ThreadLocal放值
     * @Param:
     * @return: void
     * @Author: leixiaohong
     * @Date: 6/20/21
     */
    public static void set(String key, String value) {
        Map<String, String> map = THREAD_LOCAL.get();
        if (map != null) {
            map.put(key, value);
            THREAD_LOCAL.set(map);
        } else {
            Map<String, String> initMap = new HashMap<>();
            initMap.put(key, value);
            THREAD_LOCAL.set(initMap);
        }
    }

    /***
     * @Description: 取ThreadLocal的值
     * @Param:
     * @return: java.lang.String
     * @Author: leixiaohong
     * @Date: 6/20/21
     */
    public static String get(String key) {
        Map<String, String> map = THREAD_LOCAL.get();
        return map.get(key);
    }

    /***
     * @Description: 放userId
     * @Param:
     * @return: void
     * @Author: leixiaohong
     * @Date: 6/20/21
     */
    public static final void setUserId(String userId) {
        set(USER_ID, userId);
    }

    /***
     * @Description: 取userId
     * @Param:
     * @return: java.lang.String
     * @Author: leixiaohong
     * @Date: 6/20/21
     */
    public static String getUserId() {
        return get(USER_ID);
    }

    /***
     * @Description: ThreadLocal删除防止内存溢出
     * @Param:
     * @return: void
     * @Author: leixiaohong
     * @Date: 6/20/21
     */
    public static void remove() {

        THREAD_LOCAL.remove();

    }

}

创建了ThreadLocal泛型存的map,map的key为固定userId,value为前端传给我的userId,当然我这只写了存一个userId你可以存很多值,比如openid或者bizcode,你想存啥直接往map里面扔就行。然后提供了把值往ThreadLocal放的set方法和从ThreadLocal取值的get方法,以及移除ThreadLocal里面的值的remove()方法。

Redis封装


/**
 * 功能简述:
 * @author leixiaohong
 * @date 6/20/21 12:37 AM
 */
@Component
public class CacheUserId {

    @Autowired
    private RedisTemplate redisTemplate;


    /***
     * 拿key为userId的key
     */
    public static String getKey() {
        String key = "USERID::"+UserHelper.getUserId();
        return key;
    }

    /***
     * 存redis
     */
    public void setRedisValue(String value) {
        //redis的key
        String key = getKey();
        redisTemplate.opsForValue().set(key, value);
    }

}

主要是getKey()方法我直接从UserHelper.getUserId()直接取用户的ID不用方法传参数了。那么为啥能直接get到userId呢,我只需要在前端调用我后端的时候,往ThreadLocal放一下用户的ID我在这个redis的类里面就可以直接拿了。

业务代码:

public void test() {
        String userId = "1234567";
        //往ThreadLocal放用户ID
        UserHelper.setUserId(userId);
        
        //业务逻辑......


        //拿userId
        cacheUserId.getKey();

        System.out.println("key为:" + cacheUserId.getKey());

        //缓存次信息 key为:123456 value为:雷小鸿
        cacheUserId.setRedisValue("雷小鸿");

        //移除ThreadLoacl 防止内存泄漏
        UserHelper.remove();
        
    }

这里先往ThreadLocal放用户ID,那么在redis封装的类里面就能拿到用户的ID,我这里为了测试调用了cacheUserId.getKey()如下: image.png 那么正常的逻辑redis的类里面setRedisValue再拿key

最终业务代码

    public void test(String userId) {
    
        //往ThreadLocal放用户ID
        UserHelper.setUserId(userId);

        //业务逻辑......
        
        //缓存用户信息 key为用户ID value为:雷小鸿
        cacheUserId.setRedisValue("雷小鸿");

        //移除ThreadLoacl 防止内存泄漏
        UserHelper.remove();

    }

写在最后

主要是用一个小例子讲述了,ThreadLocal在多线程之间的数据隔离,可以直接往ThreadLocal放值,然后用的时候直接取,不用把值再多个类之间传递,临时写的例子可能有错误的地方见谅,希望大家通过这个小例子打开使用ThreadLocal的大门!