ThreadLocal
簡介
ThreadLocal是Java中一個非常重要的線程技術。它可以讓每個線程都擁有自己的變量副本,避免了線程間的競爭和數據泄露問題。在本文中,我們將詳細介紹ThreadLocal的定義、用法及其優點。
ThreadLocal是Java中一個用來實現線程封閉技術的類。它提供了一個本地線程變量,可以在多線程環境下使每個線程都擁有自己的變量副本。每個線程都可以獨立地改變自己的副本,而不會影響到其他線程的副本。ThreadLocal的實現是基于ThreadLocalMap的,每個ThreadLocal對象 都對應一個ThreadLocalMap,其中存儲了線程本地變量的值。
優缺點
ThreadLocal的主要優點是可以提高并發程序的性能和安全性,同時也存在一些缺點和使用場景需要注意。
優點:
- 提高并發性能:使用ThreadLocal可以避免多個線程之間的競爭,從而提高程序的并發性能。
- 保證線程安全:每個線程有自己獨立的變量副本,避免了線程安全問題。
- 簡化代碼:使用ThreadLocal可以避免傳遞參數的繁瑣,簡化代碼。
缺點:
- 內存泄漏:ThreadLocal變量副本的生命周期與線程的生命周期一樣長,如果線程長時間存在,而ThreadLocal變量沒有及時清理,就會造成內存泄漏。
- 增加資源開銷:每個線程都要創建一個獨立的變量副本,如果線程數很多,就會增加資源開銷。
- 不適用于共享變量:ThreadLocal適用于每個線程有獨立的變量副本的場景,不適用于共享變量的場景。
適用場景
- 線程安全的對象:ThreadLocal適用于需要在多個線程中使用的線程安全對象,例如SimpleDateFormat、Random等。
- 跨層傳遞參數:ThreadLocal可以避免在方法之間傳遞參數的繁瑣,尤其在跨層傳遞參數的場景中,可以大大簡化代碼。
- 線程局部變量:ThreadLocal可以用于在當前線程中存儲和訪問局部變量,例如日志、請求信息等。
實現原理
首先通過一張圖看下ThreadLocal與線程的關系圖:
- 每個Thread對象都有一個ThreadLocalMap類型的成員變量threadLocals,這個變量是一個鍵值對集合,用于存儲每個ThreadLocal對象對應的值。
- 每個ThreadLocal對象都有一個唯一的ID,用于在ThreadLocalMap中作為鍵來存儲值。
- 當一個線程第一次調用ThreadLocal對象的get()方法時,它會先獲取當前線程的ThreadLocalMap對象,然后以ThreadLocal對象的ID作為鍵,從ThreadLocalMap中獲取對應的值。
- 如果ThreadLocalMap中不存在對應的鍵值對,則調用ThreadLocal對象的initialValue()方法來初始化一個值,并將其存儲到ThreadLocalMap中。
- 如果ThreadLocalMap對象的引用不再需要,那么需要手動將其置為null,這樣可以避免內存泄漏。
內存泄漏:
ThreadLocal變量副本的生命周期與線程的生命周期一樣長,如果線程長時間存在,而ThreadLocal變量沒有及時清理,就會造成內存泄漏。為了避免內存泄漏,可以在使用ThreadLocal的地方及時清理ThreadLocal變量,例如在線程池中使用ThreadLocal時,需要在線程結束時手動清理ThreadLocal變量。
內存泄漏出現的原因:
ThreadLocalMap中的Entry對象持有ThreadLocal對象的弱引用,但是ThreadLocalMap中的Entry對象是由ThreadLocal對象強引用的。
如果ThreadLocal對象沒有及時清理,在ThreadLocal對象被垃圾回收時,ThreadLocalMap中的Entry對象仍然存在,從而導致內存泄漏。
解決內存泄漏的方法:
在使用ThreadLocal的代碼中及時清理ThreadLocal變量。通常情況下,我們可以使用ThreadLocal的remove()方法手動清理ThreadLocal
變量,或者在使用完ThreadLocal變量后將其設置為null。
通過上圖我們可以看到,在線程方法執行過程中,ThreadLocal、ThreadLocalMap以及Thread之間的引用關系; Thread中存在一個屬性threadLocals指向了ThreadLocalMap,ThreadLocal實現線程級別的數據隔離主要是基于該對象;在ThreadLocal中是沒有存儲任何數據,其更像一個線程與ThreadLocalMap間的協調器,數據存儲在ThreadLocalMap中,但是該Map的Key卻是ThreadLocal的弱引用;
一般情況下,線程執行完成后,待線程銷毀,那么線程對應的屬性threadLocals也會被銷毀;但是真實環境中對線程的使用大部分都是線程池,這樣在整個系統生命周期中, 線程都是有效的,直至線程池關閉。而將ThreadLocalMap的Key設置成弱引用時,經過GC后該Map的Key則變成了null,但是其Value卻一直存在,因此需要手動將key為null 的數據進行清理。
下面是一個示例演示如何避免ThreadLocal內存泄漏:
public class MyThreadLocal {
private static ThreadLocal< String > threadLocal = new ThreadLocal< >();
public static void set(String value) {
threadLocal.set(value);
}
public static String get() {
return threadLocal.get();
}
public static void remove() {
threadLocal.remove();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
MyThreadLocal.set("hello");
System.out.println(MyThreadLocal.get());
// 在使用完ThreadLocal變量后,調用remove()方法清理ThreadLocal變量
MyThreadLocal.remove();
}
}
在上面的代碼中,MyThreadLocal類封裝了ThreadLocal變量的操作,MyRunnable類實現了Runnable接口,使用MyThreadLocal類來存儲和訪問 ThreadLocal變量。在MyRunnable的run()方法中,使用完ThreadLocal變量后,調用remove()方法清理ThreadLocal變量,避免了內存泄漏的問題。
ThreadLocal一般會設置成static
主要是為了避免重復創建TSO(thread specific object,即與線程相關的變量。)我們知道,一個ThreadLocal實例對應當前線程中的一個TSO實例。如果把ThreadLocal聲明為某個類的實例變量(而不是靜態變量),那么每創建一個該類的實例就會導致一個新的TSO實例被創建。而這些被創建的TSO實例是同一個類的實例。同一個線程可能會訪問到同一個TSO(指類)的不同實例,這即便不會導致錯誤,也會導致浪費!
簡單的說就是在ThreadLocalMap中,同一個線程是否有必要設置多個ThreadLocal來存儲線程變量?
示例
下面是一個簡單的例子,演示了如何使用ThreadLocal來實現線程數據隔離:
public class ThreadLocalTest {
private static ThreadLocal< String > threadLocal = new ThreadLocal< >();
public static void main(String[] args) throws InterruptedException {
new Thread(() - > {
threadLocal.set("Thread A");
System.out.println("Thread A: " + threadLocal.get());
}).start();
new Thread(() - > {
threadLocal.set("Thread B");
System.out.println("Thread B: " + threadLocal.get());
}).start();
Thread.sleep(1000);
System.out.println("Main: " + threadLocal.get());
}
}
運行結果如下:
Thread A: Thread A
Thread B: Thread B
Main: null
從輸出結果可以看出,每個線程都擁有自己的變量副本,互不影響。而在主線程中,由于沒有設置過變量副本,所以返回null。
結束語
ThreadLocal是幫助我們在多個線程間實現線程對數據獨享,并不是用來解決線程間的數據共享問題。
-
存儲
+關注
關注
13文章
4317瀏覽量
85872 -
JAVA
+關注
關注
19文章
2969瀏覽量
104783 -
程序
+關注
關注
117文章
3787瀏覽量
81072 -
多線程技術
+關注
關注
0文章
12瀏覽量
8560
發布評論請先 登錄
相關推薦
評論