上一篇 <<<Fork/Join框架
下一篇 >>>Disruptor框架
Threadlocal: 各個線程獨有的局部變量,相互之間不受影響诚纸。
它主要有四個方法initialValue()瞬沦、get()晶乔、set()和remove(),底層采用了map集合形式進行存放,key為當前線程ID砂客。
ThreadLocal的優(yōu)勢
1.多線程的情況下臭家,每個線程之間相互隔離
2.傳遞參數(shù)
ThreadLocal的應用場景
- Spring中使用的request對象、session對象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
- Spring中使用的事務傳播行為:事務標識房午、jdbc鏈接等都放在ThreadLocal中
ThreadLocal內(nèi)部結構
1.Thread類之中有一個屬性threadLocals矿辽,內(nèi)部有一個Entry數(shù)組屬性,類似于map郭厌,不同的是用set方法袋倔,非put。
2.Entry key存放的是ThreadLocal對象 折柠,value存放的是我們存儲的對象宾娜。
3.set方法調(diào)用時,判斷key是否存在扇售,如果為空則創(chuàng)建前塔。當前threadlocal為key,value存放我們?nèi)氲闹怠?br> 存在則更新value值承冰。
ThreadLocal內(nèi)存泄露問題
threadlocal里面的Entry extends WeakReference(弱引用)
弱引用的特點是只能存在于下一次gc之前华弓,發(fā)生minorgc majorgc就會被回收,造成key變?yōu)榭绽梗瑅alue還會被棧使用寂屏,也就造成了內(nèi)存泄露問題。
ThreadLocal自身的清理過程
在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
如何避免內(nèi)存泄露
- 可以自己調(diào)用remove方法將不要的數(shù)據(jù)移除避免內(nèi)存泄漏的問題
- 每次在做set方法的時候會清除之前 key為null
- 3.使用java反射機制獲取當前線程對應的ThreadLocalMap 凑保,手動移除
為什么線程中的ThreadLocal采用弱引用而不是強引用
- 如果key是為強引用:
當我們現(xiàn)在將ThreadLocal 的引用指向為null冈爹,但是每個線程中有自己獨立ThreadLocalMap還一直在繼續(xù)持有該對象涌攻,但是我們ThreadLocal 對象不會被回收欧引,就會發(fā)生ThreadLocal內(nèi)存泄漏的問題。
- 如果key是為弱引用:
當我們現(xiàn)在將ThreadLocal 的引用指向為null恳谎,Entry 中的key指向為null芝此,但是下次調(diào)用set方法的時候,會根據(jù)判斷如果key空的情況下因痛,直接刪除婚苹,有可能會發(fā)生Entry 發(fā)生內(nèi)存泄漏的問題。
不管是用強引用還是弱引用都是會發(fā)生內(nèi)存泄漏的問題鸵膏。弱引用中不會發(fā)生ThreadLocal內(nèi)存泄漏的問題膊升。
但是最終根本的原因Threadlocal內(nèi)存泄漏的問題,產(chǎn)生于ThreadLocalMap與我們當前線程的生命周期一樣谭企,如果沒有手動的刪除的情況下廓译,就有可能會發(fā)生內(nèi)存泄漏的問題。
為什么線程中的ThreadLocalMap 底層數(shù)組Entry實現(xiàn)
因為threadlocal可能會用多次债查,比如:
ThreadLocal的缺陷
1.父線程不會傳遞給子線程
private static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
threadLocal.set("123456");
System.out.println("父線程的值:"+threadLocal.get());
new Thread(()->{
System.out.println("子線程的值:"+threadLocal.get());
}).start();
}
打臃乔:
父線程的值:123456
子線程的值:null
使用InheritableThreadLocal可解決父子傳遞【原理:在調(diào)用init方法時,會將父類的值一一復制放入子類線程中】
private static ThreadLocal threadLocal = new InheritableThreadLocal();
public static void main(String[] args) {
threadLocal.set("123456");
System.out.println("父線程的值:"+threadLocal.get());
new Thread(()->{
System.out.println("子線程的值:"+threadLocal.get());
}).start();
}
打禹锿ⅰ:
父線程的值:123456
子線程的值: 123456
2.線程池傳遞問題
使用線程池后征绸,子線程不隨父線程而改變
private static ThreadLocal threadLocal = new InheritableThreadLocal();
private static ExecutorService service = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
threadLocal.set("123456");
System.out.println("父線程首次的值:"+threadLocal.get());
Thread thread = new Thread(() -> {
System.out.println("子線程的值:" + threadLocal.get());
});
service.submit(thread);
threadLocal.set("7890j");
System.out.println("父線程修改后的值:"+threadLocal.get());
service.submit(thread);
}
打印:
父線程首次的值:123456
父線程修改后的值:7890j
子線程的值:123456
子線程的值:123456
使用第三方的TransmittableThreadLocal解決線程池傳遞
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.10.2</version>
</dependency>
private final static ExecutorService execute = Executors.newFixedThreadPool(1);
private static final ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
//第一次設置值為1
threadLocal.set("1");
execute.submit(TtlRunnable.get(() ->
System.out.println(Thread.currentThread().getName()
+ " 第一次打印ThreadLocal值:" + threadLocal.get())
));
Thread.sleep(1000);
//第二次設置值為2
threadLocal.set("2");
execute.submit(TtlRunnable.get(() ->
System.out.println(Thread.currentThread().getName()
+ " 第二次打印ThreadLocal值:" + threadLocal.get())
));
Thread.sleep(1000);
execute.shutdown();
}
打佣碚肌:
pool-1-thread-1 第一次打印ThreadLocal值:1
pool-1-thread-1 第二次打印ThreadLocal值:2
在我們開發(fā)中管怠,可能要使用hystrix框架,而此框架默認使用線程池隔離缸榄,那么在我們Controller 就無法使用RequestContextHolder來獲取request渤弛。在實際開發(fā)中可以使用信號量隔離來解決這種問題,還可以重寫 hystrix中的HystrixConcurrencyStrategy并重寫wrapCallable 方法
Threadlocal 與Synchronized
1.共同特征:都可以解決線程安全問題
2.Threadlocal 占內(nèi)存碰凶、Synchronized不是很占內(nèi)存
Synchronized 當多個線程在同時共享到同一個全局變量的時候暮芭,只能有一個線程對該變量做修改操作。缺陷:效率非常低 以時間換空間方式
Threadlocal 每個線程中有獨立的緩存的局部變量欲低,以空間換時間的方式提高效率辕宏。
相關文章鏈接:
<<<多線程基礎
<<<線程安全與解決方案
<<<鎖的深入化
<<<鎖的優(yōu)化
<<<Java內(nèi)存模型(JMM)
<<<Volatile解決JMM的可見性問題
<<<Volatile的偽共享和重排序
<<<CAS無鎖模式及ABA問題
<<<Synchronized鎖
<<<Lock鎖
<<<AQS同步器
<<<Condition
<<<CountDownLatch同步計數(shù)器
<<<Semaphore信號量
<<<CyclicBarrier屏障
<<<線程池
<<<并發(fā)隊列
<<<Callable與Future模式
<<<Fork/Join框架
<<<Disruptor框架
<<<如何優(yōu)化多線程總結