ThreadLocal相關

ThreadLocal相關

1. ThreadLocal介紹

ThreadLocal,線程局部變量。ThreadLocal經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。在不少狀況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。java

在Java的多線程編程中,爲保證多個線程對共享變量的安全訪問,一般會使用synchronized來保證同一時刻只有一個線程對共享變量進行操做。這種狀況下能夠將類變量放到ThreadLocal類型的對象中,使變量在每一個線程中都有獨立拷貝,不會出現一個線程讀取變量時而被另外一個線程修改的現象。最多見的ThreadLocal使用場景爲用來解決數據庫鏈接、Session管理等。數據庫

2. ThreadLocal內部結構

image-20200819221241296

由結構圖可知,ThreadLocal的一些核心機制:編程

  • 每一個Thread線程內部都有一個Map;
  • Map裏面存儲線程本地對象(key)和線程的變量副本(value);
  • Thread內部的Map由ThreadLocal維護,由ThreadLocal負責向map獲取和設置線程變量值。

所以對於不一樣的線程,每次獲取副本值時,別的線程並不能獲取到當前線程的副本值,造成了副本的隔離,互不干擾。安全

Thread線程內部的Map類以下:多線程

public class Thread implements Runnable {
    ......
    // 與此線程有關的ThreadLocal值,由ThreadLocal類維護。
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ......
}

ThreadLocal類提供如下方法:併發

get()方法:用於獲取當前線程的副本變量值。dom

  • 獲取當前線程的ThreadLocalMap對象;
  • 從map中獲取存儲的K-V Entry節點;
  • 從Entry節點中獲取存儲的Value副本值返回;
  • map爲空的話返回初始值null,即線程變量副本爲null,在使用時須要注意判斷NullPointerException。
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();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

set()方法:用於保存當前線程的副本變量值。ide

  • 獲取當前線程的的ThreadLocalMap對象;
  • map非空時從新將ThreadLocal和新的value副本放入到map中;
  • map爲空時對成員變量ThreadLocalMap進行初始化建立,並將ThreadLocal和value副本值放入map中。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}

remove()方法:移除當前線程的副本變量值。this

public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

3. ThreadLocalMap與內存泄漏

ThreadLocalMap是ThreadLocal的內部類,沒有實現Map接口,用獨立的方式實現了Map的功能,其內部的Entry也獨立實現。線程

img

在ThreadLocalMap中,使用Entry來保存K-V結構數據。可是Entry中key只能是ThreadLocal對象,這點被Entry的構造方法已經限定死了。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap中使用的key爲ThreadLocal的弱引用,而value是強引用。因此,若是ThreadLocal沒有被外部強引用的狀況下,在垃圾回收時key會被清理掉而value不會被清理掉。這樣,ThreadLocalMap中就會出現key爲null的Entry。加入不作任何措施,value永遠沒法被GC回收,這種時候可能形成內存泄漏。

所以爲了不內存泄漏問題,在調用ThreadLocal的get()、set()方法時完成後再調用remove方法,將Entry節點和Map的引用關係移除,這樣整個Entry對象在GC Roots分析後就變成不可達了,下次GC的時候就能夠被回收。

4. 應用場景

ThreadLocal處理SimpleDateFormat線程不安全問題。

import java.text.SimpleDateFormat;
import java.util.Random;

/**
 * ThreadLocal處理SimpleDateFormat線程不安全問題
 */
public class ThreadLocalDemo implements Runnable {
    private static final ThreadLocal<SimpleDateFormat> FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(demo, "" + i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Default Formatter = " + FORMATTER.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        FORMATTER.set(new SimpleDateFormat());
        System.out.println("Thread Name = " + Thread.currentThread().getName() + "; Formatter = " + FORMATTER.get().toPattern());
    }
}

執行結果:

image-20200819235457165

由輸出可知,Thread0已經改變了FORMATTER的值,但Thread1的初始化值仍然與默認初始化值相同,其餘線程也同樣。

相關文章
相關標籤/搜索