您好,登錄后才能下訂單哦!
這篇文章主要介紹“ThreadLocal好不好用”,在日常操作中,相信很多人在ThreadLocal好不好用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”ThreadLocal好不好用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
在 Java 中,如果要問哪個類使用簡單,但用好最不簡單?我想你的腦海中一定會浮現(xiàn)出一次詞——“ThreadLocal”。
確實(shí)如此,ThreadLocal 原本設(shè)計是為了解決并發(fā)時,線程共享變量的問題,但由于過度設(shè)計,如弱引用和哈希碰撞,從而導(dǎo)致它的理解難度大和使用成本高等問題。當(dāng)然,如果稍有不慎還是導(dǎo)致臟數(shù)據(jù)、內(nèi)存溢出、共享變量更新等問題,但即便如此,ThreadLocal 依舊有適合自己的使用場景,以及無可取代的價值,比如本文要介紹了這兩種使用場景,除了 ThreadLocal 之外,還真沒有合適的替代方案。
我們以多線程格式化時間為例,來演示 ThreadLocal 的價值和作用,當(dāng)我們在多個線程中格式化時間時,通常會這樣操作。
① 2個線程格式化
當(dāng)有 2 個線程進(jìn)行時間格式化時,我們可以這樣寫:
import java.text.SimpleDateFormat; import java.util.Date; public class Test { public static void main(String[] args) throws InterruptedException { // 創(chuàng)建并啟動線程1 Thread t1 = new Thread(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(1 * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); t1.start(); // 創(chuàng)建并啟動線程2 Thread t2 = new Thread(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(2 * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); t2.start(); } /** * 格式化并打印結(jié)果 * @param date 時間對象 */ private static void formatAndPrint(Date date) { // 格式化時間對象 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); // 執(zhí)行格式化 String result = simpleDateFormat.format(date); // 打印最終結(jié)果 System.out.println("時間:" + result); } }
以上程序的執(zhí)行結(jié)果為:
上面的代碼因?yàn)閯?chuàng)建的線程數(shù)量并不多,所以我們可以給每個線程創(chuàng)建一個私有對象 SimpleDateFormat 來進(jìn)行時間格式化。
② 10個線程格式化
當(dāng)線程的數(shù)量從 2 個升級為 10 個時,我們可以使用 for 循環(huán)來創(chuàng)建多個線程執(zhí)行時間格式化,具體實(shí)現(xiàn)代碼如下:
import java.text.SimpleDateFormat; import java.util.Date; public class Test { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { int finalI = i; // 創(chuàng)建線程 Thread thread = new Thread(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); // 啟動線程 thread.start(); } } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) { // 格式化時間對象 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); // 執(zhí)行格式化 String result = simpleDateFormat.format(date); // 打印最終結(jié)果 System.out.println("時間:" + result); } }
以上程序的執(zhí)行結(jié)果為:
從上述結(jié)果可以看出,雖然此時創(chuàng)建的線程數(shù)和 SimpleDateFormat 的數(shù)量不算少,但程序還是可以正常運(yùn)行的。
③ 1000個線程格式化
然而當(dāng)我們將線程的數(shù)量從 10 個變成 1000 個的時候,我們就不能單純的使用 for 循環(huán)來創(chuàng)建 1000 個線程的方式來解決問題了,因?yàn)檫@樣頻繁的新建和銷毀線程會造成大量的系統(tǒng)開銷和線程過度爭搶 CPU 資源的問題。
所以經(jīng)過一番思考后,我們決定使用線程池來執(zhí)行這 1000 次的任務(wù),因?yàn)榫€程池可以復(fù)用線程資源,無需頻繁的新建和銷毀線程,也可以通過控制線程池中線程的數(shù)量來避免過多線程所導(dǎo)致的 CPU 資源過度爭搶和線程頻繁切換所造成的性能問題,而且我們可以將 SimpleDateFormat 提升為全局變量,從而避免每次執(zhí)行都要新建 SimpleDateFormat 的問題,于是我們寫下了這樣的代碼:
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class App { // 時間格式化對象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 執(zhí)行任務(wù) threadPool.execute(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); } // 線程池執(zhí)行完任務(wù)之后關(guān)閉 threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) { // 執(zhí)行格式化 String result = simpleDateFormat.format(date); // 打印最終結(jié)果 System.out.println("時間:" + result); } }
以上程序的執(zhí)行結(jié)果為:
當(dāng)我們懷著無比喜悅的心情去運(yùn)行程序的時候,卻發(fā)現(xiàn)意外發(fā)生了,這樣寫代碼竟然會出現(xiàn)線程安全的問題。從上述結(jié)果可以看出,程序的打印結(jié)果竟然有重復(fù)內(nèi)容的,正確的情況應(yīng)該是沒有重復(fù)的時間才對。
PS:所謂的線程安全問題是指:在多線程的執(zhí)行中,程序的執(zhí)行結(jié)果與預(yù)期結(jié)果不相符的情況。
a) 線程安全問題分析
為了找到問題所在,我們嘗試查看 SimpleDateFormat 中 format 方法的源碼來排查一下問題,format 源碼如下:
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // 注意此行代碼 calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) { int tag = compiledPattern[i] >>> 8; int count = compiledPattern[i++] & 0xff; if (count == 255) { count = compiledPattern[i++] << 16; count |= compiledPattern[i++]; } switch (tag) { case TAG_QUOTE_ASCII_CHAR: toAppendTo.append((char)count); break; case TAG_QUOTE_CHARS: toAppendTo.append(compiledPattern, i, count); i += count; break; default: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); break; } } return toAppendTo; }
從上述源碼可以看出,在執(zhí)行 SimpleDateFormat.format 方法時,會使用 calendar.setTime 方法將輸入的時間進(jìn)行轉(zhuǎn)換,那么我們想想一下這樣的場景:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
線程 1 執(zhí)行了 calendar.setTime(date) 方法,將用戶輸入的時間轉(zhuǎn)換成了后面格式化時所需要的時間;
線程 1 暫停執(zhí)行,線程 2 得到 CPU 時間片開始執(zhí)行;
線程 2 執(zhí)行了 calendar.setTime(date) 方法,對時間進(jìn)行了修改;
線程 2 暫停執(zhí)行,線程 1 得出 CPU 時間片繼續(xù)執(zhí)行,因?yàn)榫€程 1 和線程 2 使用的是同一對象,而時間已經(jīng)被線程 2 修改了,所以此時當(dāng)線程 1 繼續(xù)執(zhí)行的時候就會出現(xiàn)線程安全的問題了。
正常的情況下,程序的執(zhí)行是這樣的:
非線程安全的執(zhí)行流程是這樣的:
b) 解決線程安全問題:加鎖
當(dāng)出現(xiàn)線程安全問題時,我們想到的第一解決方案就是加鎖,具體的實(shí)現(xiàn)代碼如下:
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class App { // 時間格式化對象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 執(zhí)行任務(wù) threadPool.execute(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); } // 線程池執(zhí)行完任務(wù)之后關(guān)閉 threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) { // 執(zhí)行格式化 String result = null; // 加鎖 synchronized (App.class) { result = simpleDateFormat.format(date); } // 打印最終結(jié)果 System.out.println("時間:" + result); } }
以上程序的執(zhí)行結(jié)果為:
從上述結(jié)果可以看出,使用了 synchronized 加鎖之后程序就可以正常的執(zhí)行了。
加鎖的缺點(diǎn)
加鎖的方式雖然可以解決線程安全的問題,但同時也帶來了新的問題,當(dāng)程序加鎖之后,所有的線程必須排隊執(zhí)行某些業(yè)務(wù)才行,這樣無形中就降低了程序的運(yùn)行效率了。
有沒有既能解決線程安全問題,又能提高程序的執(zhí)行速度的解決方案呢?
答案是:有的,這個時候 ThreadLocal就要上場了。
c) 解決線程安全問題:ThreadLocal
1.ThreadLocal 介紹
ThreadLocal 從字面的意思來理解是線程本地變量的意思,也就是說它是線程中的私有變量,每個線程只能使用自己的變量。
以上面線程池格式化時間為例,當(dāng)線程池中有 10 個線程時,SimpleDateFormat 會存入 ThreadLocal 中,它也只會創(chuàng)建 10 個對象,即使要執(zhí)行 1000 次時間格式化任務(wù),依然只會新建 10 個 SimpleDateFormat 對象,每個線程調(diào)用自己的 ThreadLocal 變量。
2.ThreadLocal 基礎(chǔ)使用
ThreadLocal 常用的核心方法有三個:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
set 方法:用于設(shè)置線程獨(dú)立變量副本。沒有 set 操作的 ThreadLocal 容易引起臟數(shù)據(jù)。
get 方法:用于獲取線程獨(dú)立變量副本。沒有 get 操作的 ThreadLocal 對象沒有意義。
remove 方法:用于移除線程獨(dú)立變量副本。沒有 remove 操作容易引起內(nèi)存泄漏。
ThreadLocal 所有方法如下圖所示:
官方說明文檔:https://docs.oracle.com/javase/8/docs/api/
ThreadLocal 基礎(chǔ)用法如下:
/** * @公眾號:Java中文社群 */ public class ThreadLocalExample { // 創(chuàng)建一個 ThreadLocal 對象 private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " 存入值:" + threadName); // 在 ThreadLocal 中設(shè)置值 threadLocal.set(threadName); // 執(zhí)行方法,打印線程中設(shè)置的值 print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { try { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(threadName + " 取出值:" + result); } finally { // 移除 ThreadLocal 中的值(防止內(nèi)存溢出) threadLocal.remove(); } } }
以上程序的執(zhí)行結(jié)果為:
從上述結(jié)果可以看出,每個線程只會讀取到屬于自己的 ThreadLocal 值。
3.ThreadLocal 高級用法
① 初始化:initialValue
public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal(){ @Override protected String initialValue() { System.out.println("執(zhí)行 initialValue() 方法"); return "默認(rèn)值"; } }; public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { // 執(zhí)行方法,打印線程中數(shù)據(jù)(未設(shè)置值打印) print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(threadName + " 得到值:" + result); } }
以上程序的執(zhí)行結(jié)果為:
當(dāng)使用了 #threadLocal.set 方法之后,initialValue 方法就不會被執(zhí)行了,如下代碼所示:
public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal() { @Override protected String initialValue() { System.out.println("執(zhí)行 initialValue() 方法"); return "默認(rèn)值"; } }; public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " 存入值:" + threadName); // 在 ThreadLocal 中設(shè)置值 threadLocal.set(threadName); // 執(zhí)行方法,打印線程中設(shè)置的值 print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { try { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(threadName + "取出值:" + result); } finally { // 移除 ThreadLocal 中的值(防止內(nèi)存溢出) threadLocal.remove(); } } }
以上程序的執(zhí)行結(jié)果為:
為什么 set 之后,初始化代碼就不執(zhí)行了?
要理解這個問題,需要從 ThreadLocal.get() 方法的源碼中得到答案,因?yàn)槌跏蓟椒?initialValue 在 ThreadLocal 創(chuàng)建時并不會立即執(zhí)行,而是在調(diào)用了 get 方法只會才會執(zhí)行,測試代碼如下:
import java.util.Date; public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal() { @Override protected String initialValue() { System.out.println("執(zhí)行 initialValue() 方法 " + new Date()); return "默認(rèn)值"; } }; public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { // 得到當(dāng)前線程名稱 String threadName = Thread.currentThread().getName(); // 執(zhí)行方法,打印線程中設(shè)置的值 print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { System.out.println("進(jìn)入 print() 方法 " + new Date()); try { // 休眠 1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(String.format("%s 取得值:%s %s", threadName, result, new Date())); } }
以上程序的執(zhí)行結(jié)果為:
從上述打印的時間可以看出:initialValue 方法并不是在 ThreadLocal 創(chuàng)建時執(zhí)行的,而是在調(diào)用 Thread.get 方法時才執(zhí)行的。
接下來來看 Threadlocal.get 源碼的實(shí)現(xiàn):
public T get() { // 得到當(dāng)前的線程 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 判斷 ThreadLocal 中是否有數(shù)據(jù) if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; // 有 set 值,直接返回數(shù)據(jù) return result; } } // 執(zhí)行初始化方法【重點(diǎn)關(guān)注】 return setInitialValue(); } private T setInitialValue() { // 執(zhí)行初始化方法【重點(diǎn)關(guān)注】 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
從上述源碼可以看出,當(dāng) ThreadLocal 中有值時會直接返回值 e.value,只有 Threadlocal 中沒有任何值時才會執(zhí)行初始化方法 initialValue。
注意事項—類型必須保持一致
注意在使用 initialValue 時,返回值的類型要和 ThreadLoca 定義的數(shù)據(jù)類型保持一致,如下圖所示:
如果數(shù)據(jù)不一致就會造成 ClassCaseException 類型轉(zhuǎn)換異常,如下圖所示:
② 初始化2:withInitial
import java.util.function.Supplier; public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() { @Override public String get() { System.out.println("執(zhí)行 withInitial() 方法"); return "默認(rèn)值"; } }); public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); // 執(zhí)行方法,打印線程中設(shè)置的值 print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(threadName + " 得到值:" + result); } }
以上程序的執(zhí)行結(jié)果為:
通過上述的代碼發(fā)現(xiàn),withInitial 方法的使用好和 initialValue 好像沒啥區(qū)別,那為啥還要造出兩個類似的方法呢?客官莫著急,繼續(xù)往下看。
③ 更簡潔的 withInitial 使用
withInitial 方法的優(yōu)勢在于可以更簡單的實(shí)現(xiàn)變量初始化,如下代碼所示:
public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "默認(rèn)值"); public static void main(String[] args) { // 線程執(zhí)行任務(wù) Runnable runnable = new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); // 執(zhí)行方法,打印線程中設(shè)置的值 print(threadName); } }; // 創(chuàng)建并啟動線程 1 new Thread(runnable, "MyThread-1").start(); // 創(chuàng)建并啟動線程 2 new Thread(runnable, "MyThread-2").start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結(jié)果 System.out.println(threadName + " 得到值:" + result); } }
以上程序的執(zhí)行結(jié)果為:
了解了 ThreadLocal 的使用之后,我們回到本文的主題,接下來我們將使用 ThreadLocal 來實(shí)現(xiàn) 1000 個時間的格式化,具體實(shí)現(xiàn)代碼如下:
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class MyThreadLocalByDateFormat { // 創(chuàng)建 ThreadLocal 并設(shè)置默認(rèn)值 private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss")); public static void main(String[] args) { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); // 執(zhí)行任務(wù) for (int i = 0; i < 1000; i++) { int finalI = i; // 執(zhí)行任務(wù) threadPool.execute(new Runnable() { @Override public void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執(zhí)行時間格式化 formatAndPrint(date); } }); } // 線程池執(zhí)行完任務(wù)之后關(guān)閉 threadPool.shutdown(); // 線程池執(zhí)行完任務(wù)之后關(guān)閉 threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) { // 執(zhí)行格式化 String result = dateFormatThreadLocal.get().format(date); // 打印最終結(jié)果 System.out.println("時間:" + result); } }
以上程序的執(zhí)行結(jié)果為:
從上述結(jié)果可以看出,使用 ThreadLocal 也可以解決線程并發(fā)問題,并且避免了代碼加鎖排隊執(zhí)行的問題。
除了上面的使用場景之外,我們還可以使用 ThreadLocal 來實(shí)現(xiàn)線程中跨類、跨方法的數(shù)據(jù)傳遞。比如登錄用戶的 User 對象信息,我們需要在不同的子系統(tǒng)中多次使用,如果使用傳統(tǒng)的方式,我們需要使用方法傳參和返回值的方式來傳遞 User 對象,然而這樣就無形中造成了類和類之間,甚至是系統(tǒng)和系統(tǒng)之間的相互耦合了,所以此時我們可以使用 ThreadLocal 來實(shí)現(xiàn) User 對象的傳遞。
確定了方案之后,接下來我們來實(shí)現(xiàn)具體的業(yè)務(wù)代碼。我們可以先在主線程中構(gòu)造并初始化一個 User 對象,并將此 User 對象存儲在 ThreadLocal 中,存儲完成之后,我們就可以在同一個線程的其他類中,如倉儲類或訂單類中直接獲取并使用 User 對象了,具體實(shí)現(xiàn)代碼如下。
主線程中的業(yè)務(wù)代碼:
public class ThreadLocalByUser { public static void main(String[] args) { // 初始化用戶信息 User user = new User("Java"); // 將 User 對象存儲在 ThreadLocal 中 UserStorage.setUser(user); // 調(diào)用訂單系統(tǒng) OrderSystem orderSystem = new OrderSystem(); // 添加訂單(方法內(nèi)獲取用戶信息) orderSystem.add(); // 調(diào)用倉儲系統(tǒng) RepertorySystem repertory = new RepertorySystem(); // 減庫存(方法內(nèi)獲取用戶信息) repertory.decrement(); } }
User 實(shí)體類:
/** * 用戶實(shí)體類 */ class User { public User(String name) { this.name = name; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
ThreadLocal 操作類:
/** * 用戶信息存儲類 */ class UserStorage { // 用戶信息 public static ThreadLocal<User> USER = new ThreadLocal(); /** * 存儲用戶信息 * @param user 用戶數(shù)據(jù) */ public static void setUser(User user) { USER.set(user); } }
* 訂單類
/** * 訂單類 */ class OrderSystem { /** * 訂單添加方法 */ public void add() { // 得到用戶信息 User user = UserStorage.USER.get(); // 業(yè)務(wù)處理代碼(忽略)... System.out.println(String.format("訂單系統(tǒng)收到用戶:%s 的請求。", user.getName())); } }
倉儲類:
/** * 倉儲類 */ class RepertorySystem { /** * 減庫存方法 */ public void decrement() { // 得到用戶信息 User user = UserStorage.USER.get(); // 業(yè)務(wù)處理代碼(忽略)... System.out.println(String.format("倉儲系統(tǒng)收到用戶:%s 的請求。", user.getName())); } }
以上程序的最終執(zhí)行結(jié)果:
從上述結(jié)果可以看出,當(dāng)我們在主線程中先初始化了 User 對象之后,訂單類和倉儲類無需進(jìn)行任何的參數(shù)傳遞也可以正常獲得 User 對象了,從而實(shí)現(xiàn)了一個線程中,跨類和跨方法的數(shù)據(jù)傳遞。
使用 ThreadLocal 可以創(chuàng)建線程私有變量,所以不會導(dǎo)致線程安全問題,同時使用 ThreadLocal 還可以避免因?yàn)橐腈i而造成線程排隊執(zhí)行所帶來的性能消耗;再者使用 ThreadLocal 還可以實(shí)現(xiàn)一個線程內(nèi)跨類、跨方法的數(shù)據(jù)傳遞。
到此,關(guān)于“ThreadLocal好不好用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。