Java之ThreadLocal使用常見和方式案例講解
【面試高頻】- ThreadLocal的使用場(chǎng)景以及使用方式是怎么樣的
1 兩大使用場(chǎng)景-ThreadLocal的用途
- 典型場(chǎng)景1:每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象(通常是工具類,典型需要使用的類有SimpleDateFormat和Random)
- 典型場(chǎng)景2:每個(gè)線程內(nèi)需要保存全局變量(例如在攔截器中獲取用戶信息),可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩。
2 典型場(chǎng)景1:每個(gè)線程需要一個(gè)獨(dú)享的對(duì)象
每個(gè)Thread內(nèi)有自己的實(shí)例副本,不共享;
舉例:SimpleDateFormat。(當(dāng)多個(gè)線程共用這樣一個(gè)SimpleDateFormat,但是這個(gè)類是不安全的)
- 2個(gè)線程分別用自己的SimpleDateFormat,這沒問題;
- 后來延伸出10個(gè),那就有10個(gè)線程和10個(gè)SimpleDateFormat,這雖然寫法不優(yōu)雅,但勉強(qiáng)可以接受
- 但是當(dāng)需求變成了1000,那么必然要用線程池,消耗內(nèi)存太多;
- 但是每一個(gè)SimpleDateFormat我們都需要?jiǎng)?chuàng)建一遍,那么太耗費(fèi)new對(duì)象了,改成static共用的,所有線程都共用一個(gè)simpleDateFormat對(duì)象,但這是線程不安全的,容易出現(xiàn)時(shí)間一致的情況,在調(diào)用的時(shí)候,可加鎖來解決,但還是不優(yōu)雅;
- 用ThreadLocal來解決該問題,給每個(gè)線程分配一個(gè)simpledateformat,可這個(gè)threadlocal是安全的;
package threadlocal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 描述: 利用ThreadLocal,給每個(gè)線程分配自己的dateFormat對(duì)象,保證了線程安全,高效利用內(nèi)存 */ public class ThreadLocalNormalUsage05 { public static ExecutorService threadPool = Executors.newFixedThreadPool(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage05().date(finalI); System.out.println(date); } }); } threadPool.shutdown(); } public String date(int seconds) { //參數(shù)的單位是毫秒,從1970.1.1 00:00:00 GMT計(jì)時(shí) Date date = new Date(1000 * seconds); // SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get(); return dateFormat.format(date); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); }
3 典型場(chǎng)景2:當(dāng)前用戶信息需要被線程內(nèi)所有方法共享
- 一個(gè)比較繁瑣的解決方案是把user作為參數(shù)層層傳遞,從service-1()傳到service-2(),以此類推,但是這樣做會(huì)導(dǎo)致代碼冗余且不易維護(hù)。
- 進(jìn)階點(diǎn)就是userMap來保存,但是當(dāng)多線程同時(shí)工作時(shí),需要保證線程安全,需要用synchronized,或者concurrenthashmap,但無論用什么,都會(huì)對(duì)性能有所影響
每個(gè)線程內(nèi)需要保存全局變量,可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩
- 用ThreadLocal保存一些業(yè)務(wù)內(nèi)存(用戶權(quán)限信息,從用戶系統(tǒng)獲取到的用戶名、userId等)
- 這些信息在同一個(gè)線程內(nèi)相同,但是不同的線程使用的業(yè)務(wù)內(nèi)容是不相同的
- 在線程生命周期內(nèi),都通過這個(gè)靜態(tài)ThreadLocal實(shí)例的get()方法取得自己set過的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩
package threadlocal; /** * 描述: 演示ThreadLocal用法2:避免傳遞參數(shù)的麻煩 */ public class ThreadLocalNormalUsage06 { public static void main(String[] args) { new Service1().process(""); } } class Service1 { public void process(String name) { User user = new User("超哥"); UserContextHolder.holder.set(user); new Service2().process(); } } class Service2 { public void process() { User user = UserContextHolder.holder.get(); ThreadSafeFormatter.dateFormatThreadLocal.get(); System.out.println("Service2拿到用戶名:" + user.name); new Service3().process(); } } class Service3 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service3拿到用戶名:" + user.name); UserContextHolder.holder.remove(); } } class UserContextHolder { public static ThreadLocal<User> holder = new ThreadLocal<>(); } class User { String name; public User(String name) { this.name = name; } }
注意點(diǎn):
- 強(qiáng)調(diào)的是同一個(gè)請(qǐng)求內(nèi)(同一個(gè)線程內(nèi))不同方法見的共享;
- 不需重寫initialValue()方法,但是必須手動(dòng)調(diào)用set()方法
4 ThreadLocal方法使用總結(jié)
場(chǎng)景一:initialValue
在ThreadLocal第一次get的時(shí)候把對(duì)象給初始化出來,對(duì)象的初始化時(shí)機(jī)可以由我們控制。
場(chǎng)景二:set
如果需要保存到ThreadLocal里面的對(duì)象的生成時(shí)機(jī)不由我們隨意控制。例如攔截器生成的用戶信息,用ThreadLocal.set直接放到ThreadLocal當(dāng)中。
5 ThreadLocal原理
理清Thread,ThreadLocalMap以及ThreadLocal
主要方法介紹
- T initialValue(): 初始化
- void set(T t): 為這個(gè)線程設(shè)置一新值
- T get(): 得到這個(gè)線程對(duì)應(yīng)的value。如果是首次調(diào)用get()。則會(huì)調(diào)用initialize來得到這個(gè)值
- void remove(): 刪除這個(gè)線程得到的值
ThreadLocalMap發(fā)生沖突之后,會(huì)用線性探測(cè)法。
6 ThreadLocal使用問題內(nèi)存泄露
什么是內(nèi)存泄露:某個(gè)對(duì)象不再有用,但是占用的內(nèi)存卻不能被回收。
Value的泄露
- 在ThreadLocalMap中的每個(gè)Entry都是一個(gè)對(duì)key的弱引用,同時(shí),每個(gè)Entry都包含了一個(gè)對(duì)value的強(qiáng)引用。
- 正常情況 ,當(dāng)線程終止,保存在ThreadLocal里的value會(huì)被垃圾回收,因?yàn)闆]有任何強(qiáng)引用了。
- 但是,如果線程不終止(比如線程池需要保持很久),那么key對(duì)應(yīng)的value就不能被回收。Thread->ThreadLocalMap->Entry(key為Null)->Value
- 因?yàn)関alue和Thread之間還存在這個(gè)強(qiáng)引用鏈路,所以導(dǎo)致value無法回收,就可能出現(xiàn)OOM;JDK已經(jīng)考慮到這個(gè)問題,所以在set,remove,rehash方法中會(huì)掃描key為null,會(huì)把value也設(shè)置為null,這樣value對(duì)象就可以被回收了。
- 但是如果一個(gè)ThreadLocal不被使用,那么實(shí)際上set,remove,rehash方法也不會(huì)被調(diào)用,如果同時(shí)線程又不停止,那么調(diào)用鏈就一直存在,那么就導(dǎo)致了value的內(nèi)存泄露。
如何避免內(nèi)存泄露呢?
- 調(diào)用remove方法,就會(huì)刪除對(duì)應(yīng)的Entry對(duì)象,可以避免內(nèi)存泄露,所以使用完ThreadLocal之后,應(yīng)該調(diào)用remove方法。
7 實(shí)際應(yīng)用場(chǎng)景-在spring中的實(shí)例分析
- DateTimeContextHolder:用到了ThreadLocal
- RequestContextHolder:用到了ThreadLocal
到此這篇關(guān)于Java之ThreadLocal使用常見和方式案例講解的文章就介紹到這了,更多相關(guān)Java之ThreadLocal使用常見和方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot?整合RocketMq實(shí)現(xiàn)消息過濾功能
這篇文章主要介紹了Spring?Boot?整合RocketMq實(shí)現(xiàn)消息過濾,本文講解了RocketMQ實(shí)現(xiàn)消息過濾,針對(duì)不同的業(yè)務(wù)場(chǎng)景選擇合適的方案即可,需要的朋友可以參考下2022-06-06java得到某年某周的第一天實(shí)現(xiàn)思路及代碼
某年某周的第一天,此功能如何使用java編程得到呢?既然有了問題就有解決方法,感興趣的朋友可以了解下本文,或許會(huì)給你帶來意想不到的收獲哦2013-01-01java實(shí)現(xiàn)基于TCP協(xié)議網(wǎng)絡(luò)socket編程(C/S通信)
這篇文章主要介紹了java實(shí)現(xiàn)基于TCP協(xié)議網(wǎng)絡(luò)socket編程(C/S通信),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10Spring中@Repository注解的作用和用法以及和@Mapper的區(qū)別詳析
這篇文章主要給大家介紹了關(guān)于Spring中@Repository注解的作用和用法以及和@Mapper的區(qū)別的相關(guān)資料,注解的作用是標(biāo)識(shí)一個(gè)類為數(shù)據(jù)訪問對(duì)象,并由Spring框架進(jìn)行實(shí)例化和管理,需要的朋友可以參考下2023-09-09Maven3種打包方式中maven-assembly-plugin的使用詳解
這篇文章主要介紹了Maven3種打包方式中maven-assembly-plugin的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Spring中SmartLifecycle和Lifecycle的作用和區(qū)別
這篇文章主要介紹了Spring中SmartLifecycle和Lifecycle的作用和區(qū)別,本文通過實(shí)例代碼給大家介紹的非常詳細(xì)對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03