如圖:
##接下來我們就先用一個簡單的範例給大家展示一下ThreadLocal的基本用法
package cuit.pymjl.thradlocal; /** * @author Pymjl * @version 1.0 * @date 2022/7/1 10:56 **/ public class MainTest { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); static void print(String str) { //打印当前线程中本地内存中本地变量的值 System.out.println(str + " :" + threadLocal.get()); //清除本地内存中的本地变量 threadLocal.remove(); } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 threadLocal.set("thread1 local variable"); //调用打印方法 print("thread1"); //打印本地变量 System.out.println("after remove : " + threadLocal.get()); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 threadLocal.set("thread2 local variable"); //调用打印方法 print("thread2"); //打印本地变量 System.out.println("after remove : " + threadLocal.get()); } }); t1.start(); t2.start(); } }
ThreadLocal的原理
ThreadLocal相關類別圖set
public void set(T value) { // 1.获取当前线程(调用者线程) Thread t = Thread.currentThread(); // 2.以当前线程作为key值,去查找对应的线程变量,找到对应的map ThreadLocalMap map = getMap(t); if (map != null) { // 3.如果map不为null,则直接添加元素 map.set(this, value); } else { // 4.否则就先创建map,再添加元素 createMap(t, value); } }
void createMap(Thread t, T firstValue) { /** * 这里是创建一个ThreadLocalMap,以当前调用线程的实例对象为key,初始值为value * 然后放入当前线程的Therad.threadLocals属性里面 */ t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap getMap(Thread t) { //这里就是直接获取调用线程的成员属性threadlocals return t.threadLocals; }
public T get() { // 1.获取当前线程 Thread t = Thread.currentThread(); // 2.获取当前线程的threadlocals,即ThreadLocalMap ThreadLocalMap map = getMap(t); // 3.如果map不为null,则直接返回对应的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 4.否则,则进行初始化 return setInitialValue(); }
下面是
setInitialValue的程式碼private T setInitialValue() { //初始化属性,其实就是null T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //通过当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); //如果map不为null,则直接添加元素 if (map != null) { map.set(this, value); } else { //否则就创建,然后将创建好的map放入当前线程的属性threadlocals createMap(t, value); } //将当前ThreadLocal实例注册进TerminatingThreadLocal类里面 if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; }
/** * A thread-local variable that is notified when a thread terminates and * it has been initialized in the terminating thread (even if it was * initialized with a null value). * 一个线程局部变量, * 当一个线程终止并且它已经在终止线程中被初始化时被通知(即使它被初始化为一个空值)。 */
public void remove() { //如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }
ThreadLocal記憶體洩漏為什麼會出現記憶體洩漏?
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那麼系統GC 的時候,這個ThreadLocal勢必會被回收,如果目前執行緒再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成記憶體洩漏。
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap裡所有key為null的value。但是這些被動的預防措施並不能保證不會記憶體洩漏:如果使用強引用:我們知道,ThreadLocalMap的生命週期基本上和Thread的生命週期一樣,當前線程如果沒有終止,那麼ThreadLocalMap始終不會被GC回收,而ThreadLocalMap持有對ThreadLocal的強引用,那麼ThreadLocal也不會被回收,當線程生命週期長,如果沒有手動刪除,則會造成kv累積,從而導致OOM
如果使用弱引用:弱引用中的物件具有很短的宣告週期,因為在系統GC時,只要發現弱引用,不管堆空間是否足夠,都會將物件進行回收。而當ThreadLocal的強引用被回收時,ThreadLocalMap所持有的弱引用也會被回收,如果沒有手動刪除kv,那麼會造成value累積,也會導致OOM
對比可知,使用弱引用至少可以保證不會因為map的key累積從而導致OOM,而對應的value可以透過remove,get,set方法在下一次呼叫時被清除。可見,記憶體洩漏的根源不是弱引用,而是ThreadLocalMap的生命週期和Thread一樣長,造成累積導致的
既然問題的根源是value的累積造成OOM,那我們對症下藥,每次使用完ThreadLocal呼叫remove()
方法清理掉就行了。
以上是Java ThreadLocal類別如何使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!