重新學習了一遍ThreadLocal

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文是學習極客時間每日一課《ThreadLocal原理分析及內存泄漏演示》和《ThreadLocal如何在父子線程及線程池中傳遞?》的學習筆記。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩節小課主要討論了以下幾個問題:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過ThreadLocal來實現本地變量的原理"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocal內存泄漏的原因分析和演示"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocal內存泄漏的解決方案"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"子線程如何獲取父線程的本地變量"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用線程池時,子線程如何獲取到最新的父線程的本地變量"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"通過ThreadLocal來實現本地變量的原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在看ThreadLocal的本地變量實現原理之前,我們首先需要了解的是Java語言中引用相關的知識,這樣更利於學習ThreadLocal相關的知識。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Java中的四種引用關係"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自JDK開始,Java提供了以下四種引用關係:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"強引用(Stong Reference):對象一直存活,只要有強引用在,GC就不會回收被引用對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"軟引用(Soft Reference):對象有一次存貨的機會,在系統將要發生內存溢出之前,JVM垃圾收集器將會把這些對象實例進行第二次回收,如果這次回收沒有足夠的內存,會拋出內存溢出異常。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"弱引用(Weak Reference):被弱引用關聯的對象只能存活到下一次垃圾收集發生之前,當垃圾收集器工作時,無論當前的內存是否足夠,都會回收只被弱引用關聯的對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虛引用(Phantom Reference): 也叫幽靈引用或者幻影引用,對象隨時可能被回收。 "}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ThreadLocal來實現本地變量的原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當使用ThreadLocal維護變量時,該變量存儲在線程本地,其他線程無法訪問,做到了線程間隔離,也就沒有線程安全的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/11/112860bd52d7bef506a8a99fff4f8db1.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序運行時,棧中會存儲Thread和ThreadLocal的引用。堆中的每一個Thread中都有一個ThreadLocalMap對象,ThreadLocalMap中有一個Entry數組,一個Entry對象中,又包含一個key和一個value,key就是ThreadLocal對象實例。"},{"type":"text","marks":[{"type":"strong"}],"text":"這裏的ThreadLocal的key就是弱引用,value是通過java.lang.ThreadLocal#set方法實際寫入的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"數據存儲結構源碼分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏,主要看java.lang.ThreadLocal中的ThreadLocalMap內部靜態類"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"INITIAL_CAPACITY 初始化容量,必須爲2的整數次冪"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Entry[] table 一個Map中可以保存多個ThreadLocal對象,這些對象都存儲在table中"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"size ThreadLocal的個數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"threshold 下次擴容時,應該擴容到多大"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static class ThreadLocalMap {\n\n ...\n\n /**\n * The initial capacity -- MUST be a power of two.\n */\n private static final int INITIAL_CAPACITY = 16;\n\n /**\n * The table, resized as necessary.\n * table.length MUST always be a power of two.\n */\n private Entry[] table;\n\n /**\n * The number of entries in the table.\n */\n private int size = 0;\n\n /**\n * The next size value at which to resize.\n */\n private int threshold; // Default to 0\n \t\t\t...\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"set源碼分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個方法裏面的邏輯是數據存儲到ThreadLocal中的全過程,主要包含以下幾步:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取當前線程"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取當前線程的ThreadLocalMap對象"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果ThreadLocalMap不爲空,將對象值value設置到ThreadLocalMap中"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果ThreadLocalMap爲空時,就是首次設置,需要以當前ThreadLocal對象作爲Key創建ThreadLocalMap,並且將threadLocals這個引用指向新創建的ThreadLocalMap對象。"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void set(T value) {\n Thread t = Thread.currentThread();\n ThreadLocalMap map = getMap(t);\n if (map != null) {\n map.set(this, value);//這裏的this指的是ThreadLocal對象\n } else {\n createMap(t, value);//首次設置\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadLocal內存泄漏"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"原因分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocal並不存儲值,它只是作爲一個key,讓線程從ThreadLocalMap中獲取value,ThreadLocalMap是使用ThreadLocal的弱引用作爲key的,一個對象只剩下弱引用,則該對象在GC時就會被回收。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocalMap使用ThreadLocal的弱引用作爲key時,如果一個ThreadLocal沒有外部強引用來引用它,比如,下圖中,手動將ThreadLocal A這個對象賦值爲null,系統GC時,這個ThreadLocal A會被回收,這時,ThreadLocalMap中就會出現key爲null的Entry,Java程序無法訪問這些key爲null的Entry的value。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/21/21544d8b502dbdafca37e45e4de2ac18.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果當前線程遲遲不結束,如使用了線程池,或者當前線程還要執行其他耗時的任務,那麼這些key爲null的Entry的value就會存在下圖中,標紅的這條強引用鏈:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1e/1e14c1a9e1cc4112aaec5d72b6ab0694.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TheadRef引用Thread,Thread引用ThreadLocalMap,ThreadLocalMap又引用Entry,Entry對象又引用了value,這個Map的Key已經是null,這個value則永遠無法被回收。因爲這條強引用鏈的存在,造成了內存泄漏。只有當前線程thread結束以後,ThreadRef就不存在與棧中,強引用斷開,Thread對象、ThreadLocalMap,Entry數組、Entry對象、ObjC對象將全部被GC回收。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"內存泄漏演示"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MyThreadLocalOOM {\n public static final Integer SIZE = 500;\n static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1,\n TimeUnit.MINUTES,new LinkedBlockingDeque<>());\n static class Stu{\n private byte[] locla = new byte[1024*1024*5];\n }\n\n static ThreadLocal local = new ThreadLocal<>();\n\n public static void main(String[] args) {\n try {\n for(int i = 0; i < SIZE; i++){\n executor.execute(()->{\n local.set(new Stu());\n System.out.println(\"開始執行\");\n });\n Thread.sleep(100); \n }\n local = null; //會內存泄漏\n System.out.println(\"執行完畢\");\n }catch (InterruptedException e){\n e.printStackTrace();\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第20行代碼,將local置爲null後,總共有5*5=25MB的堆內存泄漏。for循環結束後,手動GC,visualvm監控到的堆內存使用情況如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/08/084fea62a364220f34e2c2c06b2e4dc0.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主動調用remove()方法:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MyThreadLocalOOM {\n public static final Integer SIZE = 500;\n static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1,\n TimeUnit.MINUTES,new LinkedBlockingDeque<>());\n static class Stu{\n private byte[] locla = new byte[1024*1024*5];\n }\n\n static ThreadLocal local = new ThreadLocal<>();\n\n public static void main(String[] args) {\n try {\n for(int i = 0; i < SIZE; i++){\n executor.execute(()->{\n local.set(new Stu());\n System.out.println(\"開始執行\");\n local.remove();\n });\n Thread.sleep(100); \n }\n System.out.println(\"執行完畢\");\n }catch (InterruptedException e){\n e.printStackTrace();\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"for循環執行完畢後,手動GC,效果如下,內存泄漏完美解決:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f8/f8f4c791bedd53bac488e3a1e6e20733.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"主動調用ThreadLocal對象的remove方法,將ThreadLocal對象中的值刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocal在設計時,也已經做了一些防護措施,在調用ThreadLocal的get()、set()方法操作數據時,會調用expungeStaleEntry(int staleSlot)方法清除當前線程中ThreadLocalMap中key爲null的value。如果ThreadLocal對象的強引用被刪除後,線程長時間存活,又沒有再對該線程的ThreadLocal對象進行操作,依然會造成內存泄漏。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,在使用ThreadLocal時,要主動調用remove方法,將ThreadLocal對象中的值刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadLocal提供了存儲變量的能力,這些變量都是私有的,但是實際工作當中,我們經常會遇到多個線程共同訪問同一個共享變量的情況。此時,如果對併發不是很瞭解,很可能就會造成併發問題。解決併發問題常用的手段有以下三種:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"悲觀鎖:使用簡單,鎖定粒度大,讀寫一視同仁。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"樂觀鎖:適用於寫少讀多的場景。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程本地變量:線程內存儲變量的能力。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下內容介紹的是線程本地變量的解決方案。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"子線程如何獲取父線程的變量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JDK提供了InheritableThreadLocal(ITL)可以在創建子線程時,拷貝父線程的本地變量的值到子線程本地變量中。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/38/381ec915b710853bc34dbb0a779c2e5e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代碼演示"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"使用ThreadLocal"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MyInheritableThreadLocal {\n public static ThreadLocal threadLocal = new ThreadLocal<>();\n\n public static void main(String[] args) throws InterruptedException{\n threadLocal.set(12345);\n System.out.println(\"獲取父線程本地變量:\"+threadLocal.get());\n new Thread(()-> System.out.println(\"獲取子線程本地變量:\"+threadLocal.get())).start();\n TimeUnit.SECONDS.sleep(1);\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過main執行父線程,在main方法中創建子線程,使用threadLocal.set(12345);給父線程的本地變量賦值。運行結果如下,"},{"type":"text","marks":[{"type":"strong"}],"text":"子線程獲取不到父線程的本地變量"},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a6/a63203114d6cacbab2ed441e926563b8.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"使用InheritableThreadLocal"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MyInheritableThreadLocal {\n // use TL\n// public static ThreadLocal threadLocal = new ThreadLocal<>();\n //use ITL\n public static ThreadLocal threadLocal = new InheritableThreadLocal<>();\n\n public static void main(String[] args) throws InterruptedException{\n threadLocal.set(12345);\n System.out.println(\"獲取父線程本地變量:\"+threadLocal.get());\n new Thread(()-> System.out.println(\"獲取子線程本地變量:\"+threadLocal.get())).start();\n TimeUnit.SECONDS.sleep(1);\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運行結果如下,子線程能獲取到父線程的本地變量。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/18/18f13aa3a4a2bc95e4ae772ddf44b064.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原因是,創建子線程時,ITL會拷貝一份父線程的本地變量給子線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ITL線程安全問題"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"代碼演示"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果父子線程都引用了同一個對象,會有線程安全問題。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class InheritableThreadLocalTest {\n public static ThreadLocal threadLocal = new InheritableThreadLocal<>();\n public static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));\n\n public static void main(String[] args) throws InterruptedException {\n System.out.println(\"主線程開啓\");\n threadLocal.set(new Stu(\"張三\",1));\n System.out.println(\"獲取主線程本地變量:\"+ threadLocal.get());\n executorService.submit(() -> System.out.println(\"獲取子線程本地變量:\"+threadLocal.get()));\n TimeUnit.SECONDS.sleep(1);\n threadLocal.get().setAge(2);\n System.out.println(\"主線程讀取本地變量:\"+threadLocal.get());\n executorService.submit(()-> {\n System.out.println(\"子線程獲取本地變量:\"+threadLocal.get());\n threadLocal.get().setAge(3);\n System.out.println(\"子線程獲取本地變量:\"+threadLocal.get());\n });\n TimeUnit.SECONDS.sleep(1);\n System.out.println(\"主線程讀取本地變量:\"+threadLocal.get());\n }\n}"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主線程創建姓名爲張三,年齡爲1的對象放入threadLocal中"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用獲取主線程的值,獲取主線程本地變量:Stu{name='張三', age=1}"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過線程池創建子線程,獲取子線程本地變量值:獲取子線程本地變量:Stu{name='張三', age=1}"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在子線程中設置年齡爲3,獲取子線程的本地變量子線程獲取本地變量:Stu{name='張三', age=3}"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主線程中獲取主線程本地變量值:主線程讀取本地變量:Stu{name='張三', age=3},"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}},{"type":"strong"}],"text":"子線程中的修改同時影響了主線程中的值,存在線程安全問題。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重寫ITL中的childValue方法,實現對象的深拷貝,複製到子線程中的對象就是一個全新的對象,與父線程無關。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MyInheritableThreadLocalImpl extends InheritableThreadLocal{\n @Override\n protected T childValue(T parentValue) {\n String s = JSONObject.toJSONString(parentValue);\n return (T) JSONObject.parseObject(s, parentValue.getClass());\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用線程池時,子線程獲取父線程的變量問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ITL只會在創建子線程時進行拷貝,如果使用線程池時,線程一直會存在於線程池中,後續可以用於執行多個提交到線程池的任務,此時,每次提交任務時,無法獲取父線程的本地變量。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5a/5aeff75826cf59b5eb1d7af478384b3b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"TransmittableThreadLocal"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TransmittableThreadLocal是阿里開源的用於解決在使用線程池等會緩存線程的組件情況下傳遞ThrealLocal問題的ITL的擴展,通常簡稱爲TTL,具體的實現原理就不分析了,今天寫得太長了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用方法與ITL類似,如果要傳遞對象的話,需要重寫TTL中的copy方法,代碼如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * 實現對象深拷貝\n * @param \n */\npublic class MyTransmittableThreadLocal extends TransmittableThreadLocal {\n @Override\n public T copy(T parentValue) {\n String s = JSONObject.toJSONString(parentValue);\n return (T) JSONObject.parseObject(s, parentValue.getClass());\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"寫在最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"昨天看曹政老師的公衆號文章《談談關於學歷的取捨》這篇文章時,有一段話對我觸動挺大的:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你自學學不進去,煩請果斷放棄,你說曹老師,你不經常賣課麼,賣課其實也都是自學爲主,看完課就學會了?怎麼可能,至少要投入五倍以上的課程時間來實踐和驗證課程內容。很多人報了一堆課,最後學了啥?勸退勸退。我說句難聽的話,雖然我賣了不少技術課程,但我覺得能認真學的進去的,1/5都不一定有。你真的學進去了,學紮實了,你超過80%的人。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"話說回來,如果一門技術課程你聽了就學會了,精通了,才賣你66?88?99?129?賣你5萬、10萬都是應該的!"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"別的辦法我也不會,只能用練習+寫筆記的這種本辦法,讓自己紮紮實實地學,踏踏實實地往前走。以後的學習我都會這麼做。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫作平臺真好用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章