您好,登錄后才能下訂單哦!
這篇“java中的ThreadLocal能干什么”文章,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要參考一下,對于“java中的ThreadLocal能干什么”,小編整理了以下知識點,請大家跟著小編的步伐一步一步的慢慢理解,接下來就讓我們進入主題吧。
在闡述ThreadLocal
之前,我們先來看下它的設計者是怎么描述ThreadLocal
的吧。
看完官方的描述后,結合自己的理解,ThreadLocal
提供了一種對應獨立線程內的數據訪問機制,實現了變量在線程之間隔離,在線程生命周期內獨立獲取或者設置的能力。如果我們想在線程內傳遞參數但是有不想作為方法參數的時候,ThreadLocal
就可以排上用場了。不過值得注意的是ThreadLocal
并不會解決變量共享問題。實際上從ThreadLocal
的名稱上面來看,線程本地變量也已經大致說明了它的作用,所以變量的命名還是非常重要的,要做到顧名思義。如果覺得還不是很理解,沒關系,我們可以通過以下的場景再加深下理解。
假如有以下的場景,假設只有一個數據庫連接,客戶端1、2、3都需要獲取數據庫連接來進行具體的數據庫操作,但是同一時間點只能有一個線程獲取連接,其他線程只能等待。因此就會出現數據庫訪問效率不高的問題。
那我們有沒有什么辦法能夠避免線程等待的情況呢?上述問題的根本原因是數據庫連接是共享變量,同事只能有一個線程可以進行操作。那如果三個線程都有自己的數據庫連接,互相隔離,那不就不會出現等待的問題了嘛。那么此時我么可以使用ThreadLocal
實現在不同線程中的變量隔離。可以看出來,ThreadLocal
是一種已空間換取時間的做法。
從上文中,我們了解到ThreadLocal
可以實現變量訪問的線程級別的隔離。那么它是到底如何實現的呢?這還需要結合Thread
以及ThreadLocal
的源碼來分析才能揭開ThreadLocal
實現線程隔離的神秘面紗。
public class Thread implements Runnable { ... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... }
在Thread
源碼中我們發現,它有一個threadLocals
變量,它的類型是ThreadLocal中的內部類ThreadLocalMap
。我們在看下ThreadLocalMap
的定義是怎樣的。從源碼中我們可以看出來,ThreadLocalMap
實際上就是Entry
數組,這個Entry
對應的key
實際就是ThreadLocal
的實例,value
就是實際的變量值。
public class ThreadLocal<T> { ... static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... //底層數據結構是數組 private Entry[] table; ... } ... }
通過查看上述的源碼,如果還不太好理解的話,我們再結合下現實中的例子來理解。大家都有支付寶賬戶,我們通過它來管理著我們的銀行卡、余額、花唄這些金融服務。
我們以支付寶以及支付寶賬戶進行類比,假設ThreadLocal
就是支付寶,每個支付寶賬戶實際就是單獨的線程,而賬戶中的余額屬性就相當于Thread
的私有屬性ThreadLocalMap
。我們在日常生活中,進行賬戶余額的充值或者消費,并不是直接通過賬戶進行操作的,而是借助于支付寶進行維護的。這就相當于每個線程對ThreadLocalMap
進行操作的時候也不是直接操作的,而是借助于ThreadLocal
來操作。
那么Thread
到底是怎么借助ThreadLocal
進行私有屬性管理的呢?還是需要進一步查看Thread
進行set
以及get
操作的源碼。從以下的ThreadLocal
的源碼中我們可以看出,在進行操作之前,需要獲取當前的執行操作的線程,再根據線程或者線程中私有的ThreadLocalMap
屬性來進行操作。
在進行數據獲取的時候,也是按照同樣的流程,先獲取當前的線程,再獲取線程中對應的ThreadLocalMap
屬性來進行后續的值的獲取。
經過上述的源碼的分析,我們可以得出這樣的結論,ThreadLocal
之所以可以實現變量的線程隔離訪問,實際上就是借助于Thread
中的ThreadLocalMap
屬性來進行操作。由于都是操作線程本身的屬性,因此并不會影響其他線程中的變量值,因此可以實現線程級別的數據修改隔離。
我們都知道,ThreadLocal
如果使用不當的話會出現內存泄漏的問題,那么我們就通過下面的這段代碼來分析下,內存泄漏的原因到底是什么。
/** * @author mufeng * @description 測試ThreadLocal內存溢出 * @date 2022/1/16 19:01 * @since */ public class ThreadLocalOOM { /** * 測試線程池 */ private static Executor threadPool = new ThreadPoolExecutor(3, 3, 40, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); static class Info { private byte[] info = new byte[10 * 1024 * 1024]; } private static ThreadLocal<Info> infoThreadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { threadPool.execute(() -> { infoThreadLocal.set(new Info()); System.out.println("Thread started:" + Thread.currentThread().getName()); }); Thread.sleep(100); } } }
手動進行GC
之后,我們可以發現堆中仍然有超過30M的堆內存占用,如上面的代碼,在線程池中活躍的線程會有三個,對應的value
為10M,說明在線程還存活的情況下,對應的value
并沒有被回收,因此存在內存泄漏的情況,如果存在大量線程的情況,就會出現OOM
。
當我們修改代碼在線程中進行remove
操作,手動GC之后我們發現堆內存趨近于0了,之前沒有被回收的對象已經被回收了。
以上是對于ThreadLocal
發生內存泄漏問題的演示,那么再來仔細分析下背后的原因是什么。ThreadLocal
中實際存儲數據的是ThreadLocalMap
,實際上Map
對應的key
是一個虛引用,在GC
的時候可以被回收掉,但是問題就在于key所對應的value
,它是強引用,只要線程存活,那么這條引用鏈就會一致存在,如果出現大量線程的時候就會有OOM
的風險。 所以在使用ThreadLocal
的時候一定記得要顯式的調用remove
方法進行清理,防止內存泄漏。
到這里,我相信大家對于ThreadLocal
的原理有了比較深入的理解了。結合上文中的ThreadLocal
代碼,不知道大家有沒有思考過一個問題,我們在使用ThreadLocal
的時候都是在同一個線程內進行了set
以及get
操作,那么如果set
操作與get
操作在父子線程中是否還可以正常的獲取呢?帶著這樣的疑問,我們來看下如下的代碼。
/** * @author mufeng * @description 父子線程參數傳遞 * @date 2022/1/16 9:54 * @since */ public class InheritableThreadLocalMain { private static final ThreadLocal<String> count = new ThreadLocal<>(); public static void main(String[] args) { count.set("父子線程參數傳遞!!!"); System.out.println(Thread.currentThread().getName() + ":" + count.get()); new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + count.get()); }).start(); } }
與之前代碼有所不同,ThreadLocal的設值是在main線程中進行的,但是獲取操作實際是在主線程下的子線程中進行的,大家可以分析一下運行結果是怎么樣的。
看到這個運行結果,不知道大家分析的對不對呢。實際上如果理解了上文的核心的話,這個問題應該很好分析的。ThreadLocal
獲取數據的時候,首先是需要獲取當前的線程的,根據線程獲取實際存儲數據的ThreadLocalMap
,上文代碼中設置和獲取在父子線程中進行,那肯定是獲取不到設置的數據的。但是在現實的項目開發中,我們會經常遇到需要將父線程的變量值傳遞給子線程進行處理,那么應該要怎么來實現呢?這個時候InheritableThreadLocal
就派上用場了。
/** * @author mufeng * @description 父子線程參數傳遞 * @date 2022/1/16 9:54 * @since */ public class InheritableThreadLocalMain { private static final ThreadLocal<String> count = new InheritableThreadLocal<>(); public static void main(String[] args) { count.set("父子線程參數傳遞!!!"); System.out.println(Thread.currentThread().getName() + ":" + count.get()); new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + count.get()); }).start(); } }
那么InheritableThreadLocal
到底是如何實現父子線程的參數傳遞的呢?我么還是的看看源碼中的實現原理。實際上在Thread
源碼中,除了有Threadlocal
私有屬性還有InheritableThreadLocal
私有屬性。
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ... public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... //關鍵 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ... } ... }
實際在進行子線程創建的時候,在線程初始化過程中,判斷了父線程中的inheritableThreadLocals
屬性是否為空,如果不為空的話需要進行值的復制,這樣便實現了父子線程的值傳遞。
1.SpringMVC,Spring Web MVC是一種基于Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架。2.Shiro,Apache Shiro是Java的一個安全框架。3.Mybatis,MyBatis 是支持普通 SQL查詢,存儲過程和高級映射的優秀持久層框架。4.Dubbo,Dubbo是一個分布式服務框架。5.Maven,Maven是個項目管理和構建自動化工具。6.RabbitMQ,RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。7.Ehcache,EhCache 是一個純Java的進程內緩存框架。
以上是“java中的ThreadLocal能干什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。