1.如何使用
@Component
@Aspect
@Slf4j
public class DistinguishAspect {
private static final ThreadLocal<String> userIdLocal = new ThreadLocal<>();
private static final ThreadLocal<String> sessionIdLocal = new ThreadLocal<>();
@Pointcut("execution(public * com.lima.crm.controller.*.*(..)) && (" +
"@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping))")
public void distinguish() {
}
@Around("distinguish()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
userIdLocal.set(request.getHeader("userId"));
sessionIdLocal.set(request.getHeader("sessionId"));
methodA();
methodA();
getLocal();
return joinPoint.proceed();
}
public static void getLocal() {
log.info("同步獲取到的userId:"+userIdLocal.get());
log.info("同步獲取到的sessionId:"+sessionIdLocal.get());
}
void methodA(){
log.info("methodA處理完成");
methodB();
}
void methodB(){
log.info("methodB處理完成");
}
public static Long getTenantId(){
return null;
}
}
2.使用中重點關(guān)注
成員變量ThreadLocal是可以有多個的,可以理解為業(yè)務(wù)中有多個不同的實體需要做線程隔離就可以使用多少個,沒有限制
為了方便我在同一個方法中調(diào)用了methodA() methodB(),這里我是想表示經(jīng)歷過同步各種調(diào)用(可以使別的類的方法調(diào)用,只要不是異步)
3.原理
線程隔離,可以加鎖,這是時間換空間,不可避免的要線程間互相等待,那在不考慮內(nèi)存容量的情況下,如果想要空間換時間,怎么做呢,那就是ThreadLocal.
所以關(guān)注點應(yīng)該放在ThradLocal如何與當(dāng)前線程綁定使用.
重點關(guān)注兩個方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
這個例子中我使用了Springboot的切面,相當(dāng)于當(dāng)一個request過來就是一個線程,調(diào)用ThreadLocal#set()時,會先獲取當(dāng)前線程,然后再getMap()方法中獲取當(dāng)前線程的成員變量threadLocals,類型為ThreadLocal.ThreadLocalMap,這個類型的泛型是<ThreadLocal<?> k, Object v>,其實就是一個很典型的map,然后將咱們創(chuàng)建的ThreadLocal為key,放入每個線程的局部Map中,由于是線程的私有屬性,所以不用擔(dān)心并發(fā)導(dǎo)致的臟數(shù)據(jù)問題
注意:
雖然在例子中的ThreadLocal是靜態(tài)的,但是兩個ThreadLocal變量是散落在不同的線程中的,所以建議使用靜態(tài)的,還可以節(jié)省內(nèi)存空間!也就是說ThreadLocal就是個幌子纱皆,就是為了把每個線程獨有的LocalMap引出來湾趾!
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();
}
當(dāng)了解了是如何放入的,get()方法其實就很好理解了,先獲取當(dāng)前線程,然后再獲取當(dāng)前線程的ThreadLocalMap,我覺著所有的Map類型其實都是大同小異,計算Hash,這里是以this(其實就是發(fā)起get()調(diào)用的ThreadLocal的變量)來計算Hash值,然后取模,在Entry數(shù)組中找到該entry,最終獲取到value.
4.內(nèi)存泄漏問題
ThreadLocal內(nèi)存泄漏的原因主要是因為ThreadLocal中包含了ThreadLocalMap,然而ThreadLocalMap的對象是在Thread中的派草,如果Thread沒有結(jié)束搀缠,則ThreadLocalMap一直不會釋放,假如ThreadLocalMap中設(shè)置了很多值近迁,而且沒有手動設(shè)置remove()艺普,則可能會造成內(nèi)存泄露。
為什么ThreadLocal是弱引用:
這是jdk官方解決上述內(nèi)存泄漏的解決方法,但是可惜只能解決一部分,內(nèi)存泄漏的情況還是存在.
所以最好是使用完ThreadLocal后,都調(diào)用它的remove()方法,清除數(shù)據(jù)衷敌。