欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Java并發(fā)編程之ThreadLocal

 更新時間:2022年08月01日 11:21:55   作者:DivineH  
本文主要介紹了Java并發(fā)編程之ThreadLocal,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

ThreadLocal簡介

變量值的共享可以使用public static的形式,所有線程都使用同一個變量,如果想實現(xiàn)每一個線程都有自己的共享變量該如何實現(xiàn)呢?JDK中的ThreadLocal類正是為了解決這樣的問題。

ThreadLocal類并不是用來解決多線程環(huán)境下的共享變量問題,而是用來提供線程內部的共享變量,在多線程環(huán)境下,可以保證各個線程之間的變量互相隔離、相互獨立。在線程中,可以通過get()/set()方法來訪問變量。ThreadLocal實例通常來說都是private static類型的,它們希望將狀態(tài)與線程進行關聯(lián)。這種變量在線程的生命周期內起作用,可以減少同一個線程內多個函數(shù)或者組件之間一些公共變量的傳遞的復雜度。

我們先通過一個例子來看一下ThreadLocal的基本用法:

public class ThreadLocalTest {
	static class MyThread extends Thread {
		private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
		
		@Override
		public void run() {
			super.run();
			for (int i = 0; i < 3; i++) {
				threadLocal.set(i);
				System.out.println(getName() + " threadLocal.get() = " + threadLocal.get());
			}
		}
	}
	
	public static void main(String[] args) {
		MyThread myThreadA = new MyThread();
		myThreadA.setName("ThreadA");
		
		MyThread myThreadB = new MyThread();
		myThreadB.setName("ThreadB");
		
		myThreadA.start();
		myThreadB.start();
	}
}

運行結果(不唯一):

ThreadA threadLocal.get() = 0
ThreadB threadLocal.get() = 0
ThreadA threadLocal.get() = 1
ThreadA threadLocal.get() = 2
ThreadB threadLocal.get() = 1
ThreadB threadLocal.get() = 2

雖然兩個線程都在向threadLocal對象中set()數(shù)據(jù)值,但每個線程都還是能取出自己設置的數(shù)據(jù),確實可以達到隔離線程變量的效果。

ThreadLocal源碼解析

ThreadLocal常用方法介紹

  • get()方法:獲取與當前線程關聯(lián)的ThreadLocal值。
  • set(T value)方法:設置與當前線程關聯(lián)的ThreadLocal值。
  • initialValue()方法:設置與當前線程關聯(lián)的ThreadLocal初始值。

當調用get()方法的時候,若是與當前線程關聯(lián)的ThreadLocal值已經被設置過,則不會調用initialValue()方法;否則,會調用initialValue()方法來進行初始值的設置。通常initialValue()方法只會被調用一次,除非調用了remove()方法之后又調用get()方法,此時,與當前線程關聯(lián)的ThreadLocal值處于沒有設置過的狀態(tài)(其狀態(tài)體現(xiàn)在源碼中,就是線程的ThreadLocalMap對象是否為null),initialValue()方法仍會被調用。

initialValue()方法是protected類型的,很顯然是建議在子類重載該函數(shù)的,所以通常該方法都會以匿名內部類的形式被重載,以指定初始值,例如:

public class ThreadLocalTest {
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return Integer.valueOf(1);
		}
	};
}

remove()方法:將與當前線程關聯(lián)的ThreadLocal值刪除。

實現(xiàn)原理

ThreadLocal最簡單的實現(xiàn)方式就是ThreadLocal類內部有一個線程安全的Map,然后用線程的ID作為Map的key,實例對象作為Map的value,這樣就能達到各個線程的值隔離的效果。

JDK最早期的ThreadLocal就是這樣設計的,但是,之后ThreadLocal的設計換了一種方式,我們先看get()方法的源碼,然后進一步介紹ThreadLocal的實現(xiàn)方式:

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

get()方法主要做了以下事情:

1、調用Thread.currentThread()獲取當前線程對象t;

2、根據(jù)當前線程對象,調用getMap(Thread)獲取線程對應的ThreadLocalMap對象:

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

threadLocals是Thread類的成員變量,初始化為null:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

3、如果獲取的map不為空,則在map中以ThreadLocal的引用作為key來在map中獲取對應的value e,否則轉到步驟5;

4、若e不為null,則返回e中存儲的value值,否則轉到步驟5;

5、調用setInitialValue()方法,對線程的ThreadLocalMap對象進行初始化操作,ThreadLocalMap對象的key為ThreadLocal對象,value為initialValue()方法的返回值。

從上面的分析中,可以看到,ThreadLocal的實現(xiàn)離不開ThreadLocalMap類,ThreadLocalMap類是ThreadLocal的靜態(tài)內部類。每個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例本身,value是真正需要存儲的Object。這樣的設計主要有以下幾點優(yōu)勢:

  • 這樣設計之后每個Map的Entry數(shù)量變小了:之前是Thread的數(shù)量,現(xiàn)在是ThreadLocal的數(shù)量,能提高性能;
  • 當Thread銷毀之后對應的ThreadLocalMap也就隨之銷毀了,能減少內存使用量。

ThreadLocalMap源碼分析

ThreadLocalMap是用來存儲與線程關聯(lián)的value的哈希表,它具有HashMap的部分特性,比如容量、擴容閾值等,它內部通過Entry類來存儲key和value,Entry類的定義為:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
 
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Entry繼承自WeakReference,通過上述源碼super(k);可以知道,ThreadLocalMap是使用ThreadLocal的弱引用作為Key的。

分析到這里,我們可以得到下面這個對象之間的引用結構圖(其中,實線為強引用,虛線為弱引用):

我們知道,弱引用對象在Java虛擬機進行垃圾回收時,就會被釋放,那我們考慮這樣一個問題:

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部關聯(lián)的強引用,那么在虛擬機進行垃圾回收時,這個ThreadLocal會被回收,這樣,ThreadLocalMap中就會出現(xiàn)key為null的Entry,這些key對應的value也就再無妨訪問,但是value卻存在一條從Current Thread過來的強引用鏈。因此只有當Current Thread銷毀時,value才能得到釋放。

該強引用鏈如下:

CurrentThread Ref -> Thread -> ThreadLocalMap -> Entry -> value

因此,只要這個線程對象被gc回收,那些key為null對應的value也會被回收,這樣也沒什么問題,但在線程對象不被回收的情況下,比如使用線程池的時候,核心線程是一直在運行的,線程對象不會回收,若是在這樣的線程中存在上述現(xiàn)象,就可能出現(xiàn)內存泄露的問題。

那在ThreadLocalMap中是如何解決這個問題的呢?

在獲取key對應的value時,會調用ThreadLocalMap的getEntry(ThreadLocal<?> key)方法,該方法源碼如下:

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

通過key.threadLocalHashCode & (table.length - 1)來計算存儲key的Entry的索引位置,然后判斷對應的key是否存在,若存在,則返回其對應的value,否則,調用getEntryAfterMiss(ThreadLocal<?>, int, Entry)方法,源碼如下:

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
 
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

ThreadLocalMap采用線性探查的方式來處理哈希沖突,所以會有一個while循環(huán)去查找對應的key,在查找過程中,若發(fā)現(xiàn)key為null,即通過弱引用的key被回收了,會調用expungeStaleEntry(int)方法,其源碼如下:

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
 
    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
 
    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
            (e = tab[i]) != null;
            i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
 
                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

通過上述代碼可以發(fā)現(xiàn),若key為null,則該方法通過下述代碼來清理與key對應的value以及Entry:

// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;

此時,CurrentThread Ref不存在一條到Entry對象的強引用鏈,Entry到value對象也不存在強引用,那在程序運行期間,它們自然也就會被回收。expungeStaleEntry(int)方法的后續(xù)代碼就是以線性探查的方式,調整后續(xù)Entry的位置,同時檢查key的有效性。

在ThreadLocalMap中的set()/getEntry()方法中,都會調用expungeStaleEntry(int)方法,但是如果我們既不需要添加value,也不需要獲取value,那還是有可能產生內存泄漏的。所以很多情況下需要使用者手動調用ThreadLocal的remove()函數(shù),手動刪除不再需要的ThreadLocal,防止內存泄露。若對應的key存在,remove()方法也會調用expungeStaleEntry(int)方法,來刪除對應的Entry和value。

其實,最好的方式就是將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據(jù)ThreadLocal的弱引用訪問到Entry的value值,然后remove它,可以防止內存泄露。

InheritableThreadLocal

InheritableThreadLocal繼承自ThreadLocal,使用InheritableThreadLocal類可以使子線程繼承父線程的值,來看一段示例代碼:

public class ThreadLocalTest {
	private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return Integer.valueOf(10);
		}
	};
	
	static class MyThread extends Thread {
		@Override
		public void run() {
			super.run();
			System.out.println(getName() + " inheritableThreadLocal.get() = " + inheritableThreadLocal.get());
		}
	}
	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + " inheritableThreadLocal.get() = " + inheritableThreadLocal.get());
		
		MyThread myThread = new MyThread();
		myThread.setName("線程A");
		myThread.start();
	}
}

運行結果:

main inheritableThreadLocal.get() = 10

線程A inheritableThreadLocal.get() = 10

可以看到子線程成功繼承了父線程的值。

父線程還可以設置子線程的初始值,只需要重寫InheritableThreadLocal類的childValue(T)方法即可,將上述代碼的inheritableThreadLocal 定義修改為如下方式:

private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<Integer>() {
    @Override
    protected Integer initialValue() {
        return Integer.valueOf(10);
    }
    
    @Override
    protected Integer childValue(Integer parentValue) {
        return Integer.valueOf(5);
    }
};

運行結果為:

main inheritableThreadLocal.get() = 10

線程A inheritableThreadLocal.get() = 5

可以看到,子進程成功獲取到了父進程設置的初始值。

使用InheritableThreadLocal類需要注意的一點是,如果子線程在取得值的同時,主線程將InheritableThreadLocal中的值進行更改,那子線程獲取的還是舊值。

線程中用來實現(xiàn)上述功能的ThreadLocalMap類變量為

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

InheritableThreadLocal類的實現(xiàn)很簡單,主要是重寫了ThreadLocal類的getMap(Thread)方法和createMap(Thread, T)方法,將其中操作的ThreadLocalMap變量修改為了inheritableThreadLocals,這里不再進一步敘述。

參考資料

高洪巖:《Java多線程編程核心技術

ThreadLocal和synchronized的區(qū)別

到此這篇關于深入理解Java并發(fā)編程之ThreadLocal 的文章就介紹到這了,更多相關Java ThreadLocal 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解Mybatis中萬能的Map和模糊查詢寫法

    詳解Mybatis中萬能的Map和模糊查詢寫法

    這篇文章主要介紹了Mybatis中萬能的Map和模糊查詢寫法的相關資料,幫助大家更好的理解和使用Mybatis,感興趣的朋友可以了解下
    2021-03-03
  • Spring的Bean容器介紹

    Spring的Bean容器介紹

    今天小編就為大家分享一篇關于Spring的Bean容器介紹,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Java.lang.Long.parseLong()方法詳解及示例

    Java.lang.Long.parseLong()方法詳解及示例

    這個java.lang.Long.parseLong(String s) 方法解析字符串參數(shù)s作為有符號十進制長,下面這篇文章主要給大家介紹了關于Java.lang.Long.parseLong()方法詳解及示例的相關資料,需要的朋友可以參考下
    2023-01-01
  • Java使用Arrays.asList報UnsupportedOperationException的解決

    Java使用Arrays.asList報UnsupportedOperationException的解決

    這篇文章主要介紹了Java使用Arrays.asList報UnsupportedOperationException的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Java中easypoi導入excel文件列名相同的處理方案

    Java中easypoi導入excel文件列名相同的處理方案

    這篇文章主要介紹了Java中easypoi導入excel文件列名相同的處理方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • Java實現(xiàn)微信公眾平臺朋友圈分享功能詳細代碼

    Java實現(xiàn)微信公眾平臺朋友圈分享功能詳細代碼

    這篇文章主要介紹了Java實現(xiàn)微信公眾平臺朋友圈分享功能詳細代碼,小編覺得挺不錯的,這里分享給大家,供需要的朋友參考。
    2017-11-11
  • Java設計模式之策略模式示例詳解

    Java設計模式之策略模式示例詳解

    策略模式屬于Java?23種設計模式中行為模式之一,該模式定義了一系列算法,并將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。本文將通過示例詳細講解這一模式,需要的可以參考一下
    2022-08-08
  • Springboot結合JDBC實現(xiàn)雙數(shù)據(jù)源實例

    Springboot結合JDBC實現(xiàn)雙數(shù)據(jù)源實例

    這篇文章主要為大家介紹了Springboot結合JDBC實現(xiàn)雙數(shù)據(jù)源實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Java編程代碼性能優(yōu)化

    Java編程代碼性能優(yōu)化

    本文介紹了 Java 代碼優(yōu)化的過程,總結了優(yōu)化 Java 程序的一些最佳實踐,分析了進行優(yōu)化的方法,并解釋了性能提升的原因,需要的朋友可以參考下
    2015-11-11
  • 淺談@RequestParam(required = true)的誤區(qū)

    淺談@RequestParam(required = true)的誤區(qū)

    這篇文章主要介紹了@RequestParam(required = true)的誤區(qū),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評論