您好,登錄后才能下訂單哦!
這篇“ThreadLocal使用方法是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“ThreadLocal使用方法是什么”文章吧。
1、ThreadLocal作為線程上下文副本,那么一種最常見的使用方式就是用來方法隱式傳參,通過提供的set()和get()兩個public方法來實現在不同的方法中的參數傳遞。對于編程規范來說,方法定義的時候是對參數個數是有限制的,甚至在一些大廠,對方法參數個數是有明確規定的。
2、線程安全,每個線程維持自己的變量,以免紊亂,像常用的數據庫的連接池的線程安全實現就使用了ThreadLocal。
以參數傳遞為例子,如何更好地使用ThreadLocal來實現在同一線程棧中不同方法中的參數傳遞。在參數傳遞的時候,那么都會有參數名,參數值,而ThreadLocal提供的get()和set()方法,不能直接滿足設置參數名和參數值。這種情況下就需要對ThreadLocal進一次封裝,如下代碼,維護一個map對象,然后提供setValue(key, value)和getValue(key, value)方法,就可以很方便地實現了參數的設置和獲取;在需要的地方對參數進行清理,使用remove(key)或者clear()即可實現。
import java.util.HashMap; import java.util.Map; public class ThreadLocalManger<T> extends ThreadLocal<T> { private static ThreadLocalManger<Map<String, Object>> MANGER = new ThreadLocalManger<>(); private static HashMap<String, Object> MANGER_MAP = new HashMap<>(); public static void setValue(String key, Object value) { Map<String, Object> context = MANGER.get(); if(context == null) { synchronized (MANGER_MAP) { if(context == null) { context = new HashMap<>(); MANGER.set(context); } } } context.put(key, value); } public static Object getValue(String key) { Map<String, Object> context = MANGER.get(); if(context != null) { return context.get(key); } return null; } public static void remove(String key) { Map<String, Object> context = MANGER.get(); if(context != null) { context.remove(key); } } public static void clear() { Map<String, Object> context = MANGER.get(); if(context != null) { context.clear(); } } }
繼續以參數傳遞為例子,來看看ThreadLocal使用過程中存在的問題和后果。在實際業務的功能開發中,為了提升效率,大部分情況下都會使用線程池來實現,比如數據庫的連接池、RPC請求連接池、MQ消息處理池、后臺批量job池等等;同時也可能會使用一個伴隨整個應用生命周期的線程(守護線程)來實現的一些功能,比如說心跳、監控等等。使用線程池,那么在實際生產業務中并發肯定不低,池中線程就會一直復用;守護線程一旦創建,那么就會活到應用停機。所以在這些情況下,線程的生命周期很長,在使用ThreadLocal的時候,一定要進行清理,不然就會有內存溢出的情況發生。通過以下案例來模擬內存溢出的情況。
通過一個死循環來模擬高并發場景。創建一個10個核心線程數,10個最大線程數數,60秒空閑時間的、線程名以ThreadLocal-demo-開頭的線程池,在該場景下,將有10個線程來運行,運行內容很簡單:生成一個UUID,并將其作為參數key,然后設置到線程副本中。
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.stereotype.Service; import java.util.UUID; import java.util.concurrent.*; @Service public class ThreadLocalService { ThreadFactory springThreadFactory = new CustomizableThreadFactory("TheadLocal-demo-"); ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), springThreadFactory); ExecutorService service = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); public Object setValue() { for(; ;) { try { Runnable runnable = new Runnable() { @Override public void run() { String id = UUID.randomUUID().toString(); // add ThreadLocalManger.setValue(id, "this is a value"); //do something here ThreadLocalManger.getValue(id); // clear() //ThreadLocalManger.clear(); } }; executorService.submit(runnable); } catch (Exception e) { e.printStackTrace(); break; } } return "success"; } }
以上代碼中已把clear()方法注釋掉,不做清理,觸發程序,稍微將jvm設置低一些,跑不久就會報如下OOM。
java.lang.OutOfMemoryError: GC overhead limit exceeded Exception in thread "TheadLocal-demo-9" Exception in thread "TheadLocal-demo-8" Exception in thread "TheadLocal-demo-6" Exception in thread "TheadLocal-demo-10" Exception in thread "TheadLocal-demo-7" java.lang.OutOfMemoryError: GC overhead limit exceeded Exception in thread "TheadLocal-demo-5" java.lang.OutOfMemoryError: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded at com.intellij.rt.debugger.agent.CaptureStorage.insertEnter(CaptureStorage.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.OutOfMemoryError: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded
就會發生嚴重的內存溢出,通過如下debug截圖可知,設置進去的UUID堆積在內存中,逐步變多,最終撐爆內存。
在實際的業務場景中,需要傳遞的可能有訂單號,交易號,流水號等等,這些變量往往是唯一不重復的、符合案例中的UUID情況,在不清理的情況下就會造成應用OOM,進而不可用;在分布式系統中,還能導致上下游系統不可用,進而導致整個分布式進去的不可用;如果這些信息往往還可能用在網絡傳輸中,大消息占有網絡帶寬,嚴重甚至導致網絡癱瘓。所以一個小小的細節就會置整個集群于危險之中,那么如何合理化解呢。
以上問題在于忘記清理,那么如何讓清理無感知,即不需要清理也沒有問題。根因在于線程跑完一次之后,沒有進行清理,所以可提供一個基類線程,在線程執行最后對清理進行封裝。如下代碼。提供一個BaseRunnable抽象基類,該類主要如下特點。
1、該類繼承Runnable。
2、實現setArg(key, value)和getArg(key)兩個方法。
2、在重寫的run方式中分為兩步,第一步,調用抽象方法task;第二步,清理線程副本。
有了以上3個特點,繼承了BaseRunnable的線程類,只需要在實現task方法,在task方法中實現業務邏輯,參數傳遞和獲取通過setArg(key, value)和getArg(key)兩個方法即可實現,無需再顯示清理。
public abstract class BaseRunnable implements Runnable { @Override public void run() { try { task(); } finally { ThreadLocalManger.clear(); } } public void setArg(String key, String value) { ThreadLocalManger.setValue(key, value); } public Object getArg(String key) { return ThreadLocalManger.getValue(key); } public abstract void task(); }
以上就是關于“ThreadLocal使用方法是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。