您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中ThreadLocal的用法和原理是什么”,在日常操作中,相信很多人在Java中ThreadLocal的用法和原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中ThreadLocal的用法和原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
隔離各個線程間的數據
避免線程內每個方法都進行傳參,線程內的所有方法都可以直接獲取到ThreadLocal
中管理的對象。
package com.example.test1.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class AsyncTest { // 使用threadlocal管理 private static final ThreadLocal<SimpleDateFormat> dateFormatLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); // 不用threadlocal進行管理,用于對比 SimpleDateFormat dateFormat = new SimpleDateFormat(); // 線程名稱以task開頭 @Async("taskExecutor") public void formatDateSync(String format, Date date) throws InterruptedException { SimpleDateFormat simpleDateFormat = dateFormatLocal.get(); simpleDateFormat.applyPattern(format); // 所有方法都可以直接使用這個變量,而不用根據形參傳入 doSomething(); Thread.sleep(1000); System.out.println("sync " + Thread.currentThread().getName() + " | " + simpleDateFormat.format(date)); // 線程執行完畢,清除數據 dateFormatLocal.remove(); } // 線程名稱以task2開頭 @Async("taskExecutor2") public void formatDate(String format, Date date) throws InterruptedException { dateFormat.applyPattern(format); Thread.sleep(1000); System.out.println("normal " + Thread.currentThread().getName() + " | " + dateFormat.format(date)); } }
使用junit
進行測試:
@Test void test2() throws InterruptedException { for(int index = 1; index <= 10; ++index){ String format = index + "-yyyy-MM-dd"; Date time = new Date(); asyncTest.formatDate(format, time); } for(int index = 1; index <= 10; ++index){ String format = index + "-yyyy-MM-dd"; Date time = new Date(); asyncTest.formatDateSync(format, time); } }
結果如下,可以看到沒有被 ThreadLocal
管理的變量已經無法匹配正確的format。
sync task--10 | 10-2023-04-11
sync task--9 | 9-2023-04-11
normal task2-3 | 2-2023-04-11
normal task2-5 | 2-2023-04-11
normal task2-10 | 2-2023-04-11
normal task2-6 | 2-2023-04-11
sync task--1 | 1-2023-04-11
normal task2-7 | 2-2023-04-11
normal task2-8 | 2-2023-04-11
normal task2-9 | 2-2023-04-11
sync task--6 | 6-2023-04-11
sync task--3 | 3-2023-04-11
sync task--2 | 2-2023-04-11
sync task--7 | 7-2023-04-11
sync task--4 | 4-2023-04-11
sync task--8 | 8-2023-04-11
normal task2-4 | 2-2023-04-11
normal task2-1 | 2-2023-04-11
sync task--5 | 5-2023-04-11
normal task2-2 | 2-2023-04-11
從ThreadLocal
中獲取數據的過程:
先獲取對應的線程。
通過 getMap(t)
拿到線程中的 ThreadLocalMap
ThreadLocalMap
是一個重新實現的散列表,基于兩個元素實現散列:
用戶定義的ThreadLocal
對象,例如:dateFormatLocal
。
封裝了value
的Entry
對象。
通過map.getEntry(this)
方法,根據當前的 threadlocal
對象在散列表中獲得對應的Entry
如果是第一次使用get()
, 則使用 setInitialValue()
調用用戶重寫的initialValue()
方法創建map并使用用戶指定的值初始化。
在這種設計方式下,線程死去的時候,線程共享變量ThreadLocalMap
會被銷毀。
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
注意 Entry
對象是弱引用:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; // k: ThreadLocal, v: value Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
弱引用的常見用法是:
WeakReference<RoleDTO> weakReference = new WeakReference<>(new RoleDTO());
因此,在Entry
中,k
代表ThreadLocal
對象,它是弱引用。v代表ThreadLocal
管理的那個value
,是強引用。
內存泄漏是指無用對象(不再使用的對象)持續占有內存或無用對象的內存得不到及時釋放,從而造成內存空間的浪費稱為內存泄漏。隨著垃圾回收器活動的增加以及內存占用的不斷增加,程序性能會逐漸表現出來下降,極端情況下,會引發OutOfMemoryError
導致程序崩潰。
內存泄漏問題主要在線程池中出現,因為線程池中的線程是不斷執行的,從任務隊列中不斷獲取新的任務執行。但是任務中可能有ThreadLocal
對象,這些對象的ThreadLocal
會保存在線程的ThreadLocalMap
中,因此ThreadLocalMap
會越來越大。
但是ThreadLocal
是由任務(worker)傳入的,一個任務執行結束后,對應的ThreadLocal
對象會被銷毀。線程中的關系是: Thread -> ThreadLoalMap -> Entry<ThreadLocal, Object>
。ThreadLocal
由于是弱引用會,在GC的時候會被銷毀,這會導致 ThreadLoalMap
中存在Entry<null, Object>
。
使用remove()
由于線程池中的線程一直在運行,如果不對ThreadLoalMap
進行清理,那Entry<null, Object>
會一直占用內存。remove()
方法會清除key==null
的Entry
。
使用static修飾
將ThreadLocal
設置成static
可以避免一個線程類多次傳入線程池后重復創建Entry
。例如,有一個用戶定義的線程
public class Test implements Runnable{ private static ThreadLocal<Integer> local = new ThreadLocal<>(); @Override public void run() { // do something } }
使用線程池處理10個任務。那么線程池中每個用來處理任務的線程的Thread.ThreadLocalMap
中都會保存一個Entry<local, Integer>
,由于添加了static
關鍵字,所有每個線程中的Entry
中的local
變量引用的都是同一個變量。這時就算發生內存泄漏,所有的Test類也只有一個local
對象,不會導致內存占用過多。
@Test void contextLoads() { Runnable runnable = () -> { System.out.println(Thread.currentThread().getName()); }; for(int index = 1; index <= 10; ++index){ taskExecutor2.submit(new com.example.test1.service.Test()); } }
到此,關于“Java中ThreadLocal的用法和原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。