ThreadLocal使用詳解

1、ThreadLocal的使用場景

爲線程中一個本地變量的副本提供索引,ThreadLocal能夠用來維護與當前線程相關的一些上下文,不須要經過每一個方法調用將其做爲參數傳遞。bash

使用threadLocal必定要注意內存泄漏,不然仍是建議定義context類,保存每一個線程自身上下文多線程

2、ThreadLocal分析

API
四個主要方法:優化

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
複製代碼
  • 每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。ui

  • 內部存在一個ThreadLocalMap,key爲ThreadLocal自身,值爲線程局部變量spa

  • 初始時,在Thread裏面,threadLocals(ThreadLoaclMap的實例)爲空,當經過ThreadLocal變量調用get()方法時,就會對Thread類中的threadLocals進行初始化,而且以當前ThreadLocal變量爲key,使用默認初始化的value做爲值,存到threadLocals。線程

注意: 這裏初始化的value默認是null,若是get以前沒有調用set方法,會報空指針異常,咱們建立ThreadLocal變量是能夠重寫initialValue()方法。3d

private static ThreadLocal<String> strThreadLocal = new ThreadLocal<String>() {
        public String initialValue() {
            return "default";
        }
    };
複製代碼

3、ThreadLocal內存泄漏

tips:
四種類型的引用指針

  1. 強引用
    new 對象
    Object obj = new Object();
    正常垃圾回收code

  2. 軟引用
    內存空間不夠,垃圾回收時會回收其內存
    Object obj = new Object();
    SoftReference sf = new SoftReference(obj);
    obj = null;
    sf.get();//有時候會返回nullcdn

  3. 弱引用
    弱引用的對象,能夠存活到下一次垃圾回收
    Object obj = new Object();
    WeakReference wf = new WeakReference(obj);
    obj = null;
    wf.get();//有時候會返回null
    wf.isEnQueued();//返回是否被垃圾回收器標記爲即將回收的垃圾

  4. 虛應用
    不影響對象的生命週期。
    虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。
    若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。

  5. 內存泄漏: 原本應該被回收的對象由於某種緣由沒法被回收

    ThreadLocal 中的ThreadLocMap(後面簡稱爲map),它會在線程運行的整個週期都會存在,若是map中的key和value是強引用,那麼map持有它們的引用,引用的ThreadLocal的對象被回收了,可是整個Entry仍是存在,形成內存泄漏。

    對此,Threadlocal有如下優化:

    1. map中key使用弱引用,在一次gc後,能夠會被自動回收,變成null
    2. 每次調用set,get,remove方法會自動清除value對應爲空的對象

    在多線程不斷set放入一個大對象的時候,其中一個線程運行週期比較長,在這期間沒有發生gc,ThreadLocalMap中的key就不會及時回收,或者程序運行異常中斷,set以後不再會調用threadLocal相關方法,都會形成內存泄漏。

    爲了不內存泄漏,建議使用remove方法:

    //僞代碼
    try {
        threadLocal.set()
        業務邏輯...
        threadLocal.get()
    } catch(){
    } finally{
        threadLocal.remove()
    }
    複製代碼

    簡單代碼示例:

    public class Test {
        ThreadLocal<String> stringLocal = new ThreadLocal<String>() {
            public String initialValue() {
                return "default";
            }
        };
        ThreadLocal<Long> longLocal = new ThreadLocal<Long>() {
            public Long initialValue() {
                return 0L;
            }
        };
    
        public void set() {
            longLocal.set(Thread.currentThread().getId());
            stringLocal.set(Thread.currentThread().getName());
        }
    
        public void get() {
            System.out.println("longLocal: " + longLocal.get());
            System.out.println("stringLocal: " + stringLocal.get());
        }
    
        public static void main(String[] args) throws InterruptedException {
            final Test test = new Test();
            System.out.println("獲取默認值");
            test.get();
            System.out.println("----------");
            System.out.println("主線程:");
            test.set();
            test.get();
            System.out.println("----------");
            //開啓一個線程
            Thread t1 = new Thread() {
                public void run() {
                    System.out.println("t1線程:");
                    test.set();
                    test.get();
                }
            };
    
            t1.start();
            t1.join();
            System.out.println("-----------");
            System.out.println("驗證主線程:");
            test.get();
            //在主線程和t1線程中,各自建立了一個變量副本,stringLocal ,longLocal不同
        }
    }
    
    
    結果:  
    獲取默認值
    longLocal: 0
    stringLocal: default
    ----------
    主線程:
    longLocal: 1
    stringLocal: main
    ----------
    t1線程:
    longLocal: 11
    stringLocal: Thread-0
    -----------
    驗證主線程:
    longLocal: 1
    stringLocal: main
    複製代碼
相關文章
相關標籤/搜索