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()
如下:
那么正常的逻辑redis的类里面setRedisValue再拿key
最终业务代码
public void test(String userId) {
//往ThreadLocal放用户ID
UserHelper.setUserId(userId);
//业务逻辑......
//缓存用户信息 key为用户ID value为:雷小鸿
cacheUserId.setRedisValue("雷小鸿");
//移除ThreadLoacl 防止内存泄漏
UserHelper.remove();
}
写在最后
主要是用一个小例子讲述了,ThreadLocal在多线程之间的数据隔离,可以直接往ThreadLocal放值,然后用的时候直接取,不用把值再多个类之间传递,临时写的例子可能有错误的地方见谅,希望大家通过这个小例子打开使用ThreadLocal的大门!