作用
使得每个线程都有自己的本地变量。使得线程独立使用相关数据,从而不受干扰。
即,保证线程安全。
使用举例
参考苍穹外卖中对于用户ID的处理。
目标:将empId存储在ThreadLocal中,当处理当前用户请求时,可以直接从ThreadLocal中获取empId
1. 用户登录,生成jwt令牌
当用户登录时,将empId放入jwt的claims部分,并将生成的token返回给前端
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
2. 前端进行请求时,携带token
前端每次进行请求时,需要携带token。当token返回至后端时,需要通过拦截器拦截请求,并进行token校验。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// System.out.println("当前线程的id:" + Thread.currentThread().getId());
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
BaseContext.setCurrentId(empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
3. 解析token,将信息放入线程上下文中
当解析出empId后,需要调用BaseContext.setCurrentId(empId)
方法,将empId放入上下文。
而调用该方法时,需要使用到ThreadLocal
:
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
查看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.currentThread();
获取当前线程t; -
通过getMap(t)获取到当前线程的
ThreadLocalMap
; -
将empId信息存入这个map中,key为当前线程t,value为用户信息empId。
4. 从ThreadLocal中取出用户信息
即利用BaseContext.getCurrentId()
来取出empId:
Long userId = BaseContext.getCurrentId();
//BaseContext类
public static Long getCurrentId() {
return threadLocal.get();
}
其调用了ThreadLocal
的get()
方法:
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();
}
-
利用
Thread.currentThread()
获取当前线程; -
get当前线程的ThreadLocalMap;
-
获取map中的信息
底层原理
1. ThreadLical类定义:
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
public ThreadLocal() {
}
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
-
可以发现,当调用ThreadLocal类中的方法时,首先都需要获取当前Thread t,然后再获取t的ThreadLocalMap map,最后通过操作该map实现一系列操作。
-
并且ThreadLocal类中存在一个
ThreadLocalMap
内部类。
ThreadLocalMap内部类
其中数组table用于存放我们保存在ThreadLocal中的内容。
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private Entry[] table; //关键结构,用于保存数据 Entry[key, value],key通常为当前线程Thread
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
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);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
if (e.refersTo(key))
return e;
if (e.refersTo(null))
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private void set(ThreadLocal<?> key, Object value) {
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)]) {
if (e.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (Entry e : oldTab) {
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
}
2. Thread类定义
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private final long stackSize;
/*
* Thread ID
*/
private final long tid;
}
在Thread类中,定义了两个ThreadLocal.ThreadLocalMap,分别为threadLocals和inheritableThreadLocals,并且将二者始化为null。
因此,可以发现,当调用ThreadLocal
的set()方法时,
-
首先,调用Thread类的构造方法,创建一个Thread实例,此时会初始化ThreadLocalMap;
-
其次调用
ThreadLocalMap
的getMap()
方法,这份方法中返回的t.threadLocals
即为初始化的ThreadLocalMap
内部类; -
调用
ThreadLocalMap
中的set(this, value)
方法,将当前线程t
作为key
,待保存的值为value
保存到map
中的Entry[] table
数组中。
原理总结:3个类
-
ThreadLocal类:提供给用户操作的接口
-
ThreadLocalMap内部类(在ThreadLocal类中):类似于HashMap,用于存放相关数据
-
Thread类:指代当前线程