中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何掌握ThreadLocal的相關知識點

發布時間:2021-10-23 16:15:18 來源:億速云 閱讀:119 作者:iii 欄目:編程語言

本篇內容介紹了“如何掌握ThreadLocal的相關知識點”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

一、介紹

根據 Java 官方文檔的描述,我們可知 ThreadLocal 類用于提供線程內部的局部變量,其在多線程環境下能保證各個線程內部變量的隔離性。

換言之,ThreadLocal 提供線程內的局部變量,不同線程之間不會相互干擾,該變量作用范圍貫穿線程的生命周期,減少同一線程內多個方法或組件之間一些公共變量傳遞的復雜度。

二、使用

2.1 常用方法

返回值方法名描述
Tget()返回此線程局部變量的當前線程副本中的值
voidremove()移除此線程局部變量當前線程的值
voidset(T value)將此線程局部變量的當前線程副本中的值設置為指定值

2.2 案例演示

需求:用 3 名畫家在一個畫布上各自繪制一種顏色,并打印出其繪制的顏色。

/**
 * 畫布類
 */
public class Canvas {
	
	private String content;

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
}

/**
 * 畫家類
 */
public class Painter extends Thread {

	private String name;
	
	private Canvas canvas;
	
	private String color;
	
	public Painter(String name, Canvas canvas, String color) {
		this.name = name;
		this.canvas = canvas;
		this.color = color;
	}

	@Override
	public void run() {
		canvas.setContent(color);
		System.out.println(this.name + "在畫板繪制" + canvas.getContent());
		
	}
}

/**
 * 啟動類
 */
public class Demo {

	public static void main(String[] args) {
		
		// 創建畫布
		Canvas canvas = new Canvas();
		
		Painter painter1 = new Painter("小強", canvas, "紅色");
		Painter painter2 = new Painter("旺財", canvas, "黃色");
		Painter painter3 = new Painter("狗蛋", canvas, "藍色");
		
		painter1.start();
		painter2.start();
		painter3.start();
		
	}
}

執行結果如下:

小強在畫板繪制藍色
旺財在畫板繪制黃色
狗蛋在畫板繪制黃色

顯然,在多線程訪問同一個資源(畫布)的情況下,輸出結果出現并發問題。

現有 2 種解決方案:一種是在 run 方法中加入 synchronized 同步代碼塊,另一種是使用 ThreadLocal 改造 Canvas 類型。

由于本篇著重介紹 ThreadLocal, 故下邊我們通過第二種方式解決上述問題。

修改 Canvas 類為如下:

public class Canvas {
	
	private ThreadLocal<String> map = new ThreadLocal();

	public String getContent() {
		return map.get();
	}

	public void setContent(String content) {
		map.set(content);
	}
}

啟動執行類,運行結果如下:

小強在畫板繪制紅色
狗蛋在畫板繪制藍色
旺財在畫板繪制黃色

結果正常輸出。

2.3 ThreadLocal 與 synchronized 區別

名稱原理側重點
ThreadLocal空間換時間,每個線程都都提供一份變量副本,從而實現同時訪問而不相互干擾多線程之間資源相互隔離
synchronized時間換空間,只提供一個變量,讓線程排隊訪問多線程之間共享資源,同步訪問

三、ThreadLocal 內部結構

在看源碼之前,我們可以試著猜測 ThreadLocal 內部結構是怎樣的。

比如,ThreadLocal 內部定義了一個 Map 容器。當調用 ThreadLocal 實例的 set 方法時,以當前線程名/當前線程實例作為 key, 需要保存的內容作為 value 進行操作。當調用 get 方式時,以當前線程名/當前線程實例作為 key 獲取數據。

上述方案看似可以正常實現功能,實則存在一些問題:

1) 由 ThreadLocal 維護 key-value 容器,當線程增多并調用 ThreadLocal 實例 的set 方法時,key-value 容器也隨之增大,即內存占用也隨之增大。

2) 當調用 ThreadLocal 實例方法的對象為線程池中的線程時,無法區分線程是否被循環使用,即當前線程之前已從線程池中被拿出調用 ThreadLocal 實例的 set 方法,如果當前調用 get 方法就會取出之前的數據造成數據污染等問題。

那么,ThreadLocal 內部到底是怎么實現線程間內部變量的隔離性的呢?

如何掌握ThreadLocal的相關知識點

如上圖,由 Thread 實例內部維護名為 ThreadLocalMap 的容器,其元素是以 ThreadLocal 實例為 key ,保存對象作為 value 的數據結構,與我們猜測的實現方式相反。

對比我們之前設想的方案,JDK 實現方案有 2 個好處:

1) Map 存儲的 Entry 數量變少

2) 當線程銷毀時,ThreadLocalMap 也隨之銷毀,減少內存使用

四、源碼分析

4.1 ThreadLocal 源碼

我們針對常用的 setgetremove 方法進行源碼剖析。

public void set(T value) {
    // 獲取當前線程對象
    Thread t = Thread.currentThread();
    // 獲取當前線程對象維護的 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 如果 map 存在設置 entry
        map.set(this, value);
    else
        // 如果 map 不存在,由于 threadLocal 實例幫忙創建并綁定數據
        createMap(t, value);
}

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

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

set 方法執行流程:

1) 獲取當前線程對象
2) 通過當前線程對象獲取 ThreadLocalMap 對象
3) 如果 ThreadLocalMap 對象存在,則將入參設置進 ThreadLocalMap 對象中
4) 如果 ThreadLocalMap 對象不存在,則給當前線程創建 ThreadLocalMap 對象并設置入參

<hr>

public T get() {
    // 獲取當前線程對象
    Thread t = Thread.currentThread();
    // 獲取當前線程對象維護的 ThreadLocalMap 對象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 如果 map 不為空,以當前的 ThreadLocal 實例為 key, 獲取數據
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果 map 為空,初始化值,通常為 null
    return setInitialValue();
}

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;
}

protected T initialValue() {
    return null;
}

get 方法執行流程:

1) 獲取當前線程對象
2) 通過當前線程對象獲取 ThreadLocalMap 對象
3) 如果 ThreadLocalMap 對象存在,則以當前的 ThreadLocal 實例為 key, 獲取數據
4) 如果 ThreadLocalMap 對象不存在,則通過 initialValue 方法初始化 value 值。

<hr>

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

remove 方法執行流程:

1) 通過當前線程對象獲取 ThreadLocalMap 對象
2) 如果 ThreadLocalMap 對象存在,則以當前的 ThreadLocal 實例為 key, 進行數據刪除

4.2 ThreadLocalMap 源碼

ThreadLocalMapThreadLocal 的內部類,其沒有實現 Map 接口,單獨實現了 Map 的功能。

成員變量:

/**
 * 初始容量,必須是 2 的整次冪
 */
private static final int INITIAL_CAPACITY = 16;

/**
 * 存放數據的 table,數據長度也是 2 的整次冪
 */
private Entry[] table;

/**
 * 數組中 entry 的個數
 */
private int size = 0;

/**
 * 進行擴展的閥值
 */
private int threshold; // Default to 0

Entry 內部類:

static class Entry extends WeakReference<ThreadLocal<?>> {

    Object value;

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

Entry 繼承 WeakReference 類,也就是 key 是弱引用,其目的是將 ThreadLocal 對象的生命周期與線程的生命周期解綁。

五、內存泄漏

雖然 ThreadLocal 作為弱引用 key 來使用,但是在某些情況下還是會造成內存泄漏問題。 在分析內存泄漏之前,我們先補充幾個概念:

內存溢出:沒有足夠的內存供申請者使用

內存泄漏:程序中已動態分配的堆內存由于某種原因未釋放或無法釋放,造成系統內存浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果,該問題最終會導致內存溢出

強引用:常見的對象引用,只要還有強引用指向一個對象,表明對象還“活著”,垃圾回收器就不會回收該對象

弱引用:垃圾回收期一旦發現只具有弱引用指向的對象,不管當前內存空間是否足夠,都會回收該對象

了解了基本概念,接下來我們分析使用 ThreadLocal 出現內存泄漏的情況:

如何掌握ThreadLocal的相關知識點

上圖為一個線程使用 ThreacLocal 時的內存結構圖,實線箭頭表示強引用,虛線箭頭表示弱引用。

當 ThreadLocal 使用結束,棧內存的 ThreadLocal 引用被回收,即引用 1 不再指向 ThreadLocal 對象。

由于引用 2 是弱引用,沒有任何強引用指向 ThreadLocal 對象,因此 ThreadLocal 對象會被 GC 回收,此時 Entry 的 key = null

如果我們沒有會手動刪除 Entry 對象,且當前線程一直在運行中,會存在一個強引用鏈 Thread 引用-> Thread 對象-> ThreadLocal 對象-> Entry 對象 -> Value,由于 value 不會被回收,而 key 又為 null, value 這塊內存就永遠無法被訪問,這就造成了內存泄漏,

既然使用弱引用作為 ThreadLocalMap 的 key 會造成內存泄漏,那為什么還要使用它呢?

其實,在 ThreadLocalMapsetgetEntry 方法中,會對 key 為 null 進行判斷,如果為 null, 那么會將 value 也設置為 null。

換言之,在使用 ThreadLocal 的線程依然運行的情況下,我們忘記調用 remove 方法,弱引用比強引用多一層保障。弱引用指向的 ThreadLocal 對象被回收,對應的 value 在 TheadLocalMap 調用 setgetEntryremove 任一方法時被設置為 null, 避免內存泄漏。

六、總結

適用于多線程并發場景

使用 ThreadLocal 在同一線程,不同組件中可傳遞公共變量

每個線程的變量都是相互獨立,互不影響

“如何掌握ThreadLocal的相關知識點”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

广昌县| 垦利县| 平原县| 临武县| 襄樊市| 武穴市| 古交市| 奎屯市| 沙洋县| 正蓝旗| 潼关县| 通许县| 合川市| 阜平县| 阜阳市| 麟游县| 河间市| 中方县| 北票市| 莲花县| 应城市| 东方市| 紫云| 安泽县| 泽州县| 西乌珠穆沁旗| 南城县| 武冈市| 枣庄市| 奉贤区| 杂多县| 比如县| 黎川县| 墨江| 贵阳市| 如皋市| 乐都县| 钦州市| 望江县| 巴中市| 五台县|