浅谈ThreadLocal
1. ThreadLocal簡介
ThreadLocal叫做線程變量,意思是ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的,也就是說該變量是當前線程獨有的變量。ThreadLocal為變量在每個線程中都創建了一個副本,那么每個線程可以訪問自己內部的副本變量。
ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本。這里有幾點需要注意:
- 因為每個 Thread 內有自己的實例副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來。
- 既然每個 Thread 有自己的實例副本,且其它 Thread 不可訪問,那就不存在多線程間共享的問題。
ThreadLocal 提供了線程本地的實例。它與普通變量的區別在于,每個使用該變量的線程都會初始化一個完全獨立的實例副本。ThreadLocal 變量通常被private static修飾。當一個線程結束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。
總的來說,ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。
2. ThreadLocal與Synchronized的區別
ThreadLocal其實是與線程綁定的一個變量。ThreadLocal和Synchonized都用于解決多線程并發訪問。
但是ThreadLocal與synchronized有本質的區別:
Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
Synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本
,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。
一句話理解ThreadLocal,向ThreadLocal里面存東西就是向它里面的Map存東西的,然后ThreadLocal把這個Map掛到當前的線程底下,這樣Map就只屬于這個線程了。
3. 兩大使用場景
3.1 典型場景1:
每個線程需要一個獨享的對象(通常是工具類,典型需要使用的類有 SimpleDate Format和 Random)
public class ThreadLocalNormalUsage00 {private static ExecutorService threadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException {for (int j = 0; j < 1000; j++) {int i = j;threadPool.execute(() -> System.out.println(date(i)));Thread.sleep(100L);}threadPool.shutdown();}public static String date(int seconds) {return ThreadSafeFormatter.dateFormatThreadLocal.get().format(new Date(seconds * 1000L));} } class ThreadSafeFormatter {public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }3.2 典型場景2:
每個線程內需要保存全局變量(例如在攔截器中獲取用戶信息),可以讓不同方法直接使用,避免參數傳遞的麻煩。例如用 Threadlocal保存一些業務內容(用戶權限信息、從用戶系統取到的用戶名、 user id等),這些信息在同一個線程內相同,但是不同的線程使用的業務內容是不相同的。
package com.zeny.threadstudy.threadpool;public class ThreadLocalNormalUsage06 {public static void main(String[] args) {new Service1().process();} } class Service1 {public void process() {User user = new User("超哥");UserContextHolder.holder.set(user);new Service2().process();} } class Service2 {public void process() {User user = UserContextHolder.holder.get();System.out.println("Service2 get user message: " + user);new Service3().process();} } class Service3 {public void process() {User user = UserContextHolder.holder.get();System.out.println("Service3 get user message: " + user);UserContextHolder.holder.remove();} } class UserContextHolder {public static ThreadLocal<User> holder = new ThreadLocal<>(); } class User {String name;public User(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';} }4.Threadlocal的兩個作用
1.讓某個需要用到的對象在線程間隔離(每個線程都有自己的獨立的對象)
2.在任何方法中都可以輕松獲取到該對象
5.ThreadLocal原理
5.1 ThreadLocal的set()方法
public void set(T value) {//1、獲取當前線程Thread t = Thread.currentThread();//2、獲取線程中的屬性 threadLocalMap ,如果threadLocalMap 不為空,//則直接更新要保存的變量值,否則創建threadLocalMap,并賦值ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else// 初始化thradLocalMap 并賦值createMap(t, value);}從上面的代碼可以看出,ThreadLocal 在set賦值的時候首先會獲取當前線程thread,并獲取thread線程中的ThreadLocalMap屬性。如果map屬性不為空,則直接更新value值,如果map為空,則實例化threadLocalMap,并將value值初始化。那么ThreadLocalMap又是什么呢,還有createMap又是怎么做的,我們繼續往下看。大家最后自己再idea上跟下源碼,會有更深的認識。
static class ThreadLocalMap {
可看出ThreadLocalMap是ThreadLocal的內部靜態類,而它的構成主要是用Entry來保存數據 ,而且還是繼承的弱引用。在Entry內部使用ThreadLocal作為key,使用我們設置的value作為value。
//這個是threadlocal 的內部方法 void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}//ThreadLocalMap 構造方法 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}5.2 ThreadLocal的get方法
public T get() {//1、獲取當前線程Thread t = Thread.currentThread();//2、獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);//3、如果map數據為空,if (map != null) {//3.1、獲取threalLocalMap中存儲的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//如果是數據為null,則初始化,初始化的結果,TheralLocalMap中存放key值為threadLocal,值為nullreturn setInitialValue();}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}5.3 ThreadLocal的remove()方法
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}remove方法,直接將ThrealLocal 對應的值從當前相差Thread中的ThreadLocalMap中刪除。為什么要刪除,這涉及到內存泄露的問題。實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個對象只存在弱引用,那么在下一次垃圾回收的時候必然會被清理掉。所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。ThreadLocal其實是與線程綁定的一個變量,如此就會出現一個問題:如果沒有將ThreadLocal內的變量刪除(remove)或替換,它的生命周期將會與線程共存。通常線程池中對線程管理都是采用線程復用的方法,在線程池中線程很難結束甚至于永遠不會結束,這將意味著線程持續的時間將不可預測,甚至與JVM的生命周期一致。舉個例字,如果ThreadLocal中直接或間接包裝了集合類或復雜對象,每次在同一個ThreadLocal中取出對象后,再對內容做操作,那么內部的集合類和復雜對象所占用的空間可能會開始持續膨脹。
5.4 ThreadLocal與Thread,ThreadLocalMap之間的關系
Thread、THreadLocal、ThreadLocalMap之間啊的數據關系圖
從這個圖中我們可以非常直觀的看出,ThreadLocalMap其實是Thread線程的一個屬性值,而ThreadLocal是維護ThreadLocalMap
這個屬性指的一個工具類。Thread線程可以擁有多個ThreadLocal維護的自己線程獨享的共享變量(這個共享變量只是針對自己線程里面共享)
6.ThreadLocal出現空指針問題
如下代碼,當執行get方法的時候,會獲取null,然后進行拆箱操作,因為對象為空,所以報空指針異常。
package com.zeny.threadstudy.threadpool;public class Main {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public int getValue() {return threadLocal.get();}public void setValue(int value) {threadLocal.set(value);}public static void main(String[] args) {Main main = new Main();main.getValue(); //會出現空指針異常main.setValue(10);main.getValue();} }原文鏈接:https://blog.csdn.net/u010445301/article/details/111322569
總結
以上是生活随笔為你收集整理的浅谈ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MATSim笔记01-controler
- 下一篇: 产品原型设计:使用axure动态面板实现