日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

(十三)Thread-Specific Storage(ThreadLocal)模式

發布時間:2023/12/16 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (十三)Thread-Specific Storage(ThreadLocal)模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、定義

Thread-Specific Storage就是“線程獨有的存儲庫”,該模式會對每個線程提供獨有的內存空間。java.lang.ThreadLocal類提供了該模式的實現,ThreadLocal的實例是一種集合(collection)架構,該實例管理了很多對象,可以想象成一個保管有大量保險箱的房間。

?

java.lang.ThreadLocal類的方法:

  • public void set()

該方法會檢查當前調用線程,默認以該線程的Thread.currentThread()值作為鍵,來保存指定的值。

  • public Object get()

該方法會檢查當前調用線程,默認以該線程的Thread.currentThread()值作為鍵,獲取保存指定的值。

?

?

?

二、模式案例

?

//實際執行記錄日志的類,每個線程都會擁有該類的實例 public class TSLog {private PrintWriter writer = null;public TSLog(String filename){try {writer = new PrintWriter(new FileWriter(filename));} catch (IOException e) {e.printStackTrace();}}public void println(String s){writer.println(s);}public void close(){writer.println("==== End of log ====");writer.close();} }

?

public class Log {private static final ThreadLocal<TSLog> tsLogCollection = new ThreadLocal<TSLog>();public static void println(String s){getTSLog().println(s);}private static TSLog getTSLog() {TSLog tsLog = tsLogCollection.get();if(tsLog==null){tsLog = new TSLog(Thread.currentThread().getName()+"-log.txt");tsLogCollection.set(tsLog);}return tsLog;}public static void close(){getTSLog().close();} }

?

public class ClientThread extends Thread {public ClientThread(String name) {super(name);}@Overridepublic void run() {System.out.println(getName()+" BEGIN");for (int i = 0; i < 10; i++) {Log.println("i = "+i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}Log.close();System.out.println(getName()+" END");} }

?

public class Main {public static void main(String[] args) {new ClientThread("Alice").start();new ClientThread("Bobby").start();new ClientThread("Chris").start();} }

?

?

打開硬盤里的項目目錄,可以看到三個生成的日志文件

文件內容都是以下圖示

?

?

Alice、Boddy、Chris三個線程調用Log類的同一個方法,但實際上每個線程都擁有獨自的TSLog實例。

?

?

?

?

三、模式講解

Thread-Specific Storage模式的角色如下:

  • Client(委托人)

Client會將工作委托給TSObjectProxy。(案例中的ClientThread類就是Client)

  • TSObjectProxy(線程獨有對象的代理者)

TSObjectProxy會處理多個Client委托的工作。(案例中的Log類就是TSObjectProxy)

  • TSObjectCollection(線程獨有對象的集合)

案例中的java.lang.ThreadLocal類就是TSObjectCollection

  • TSObject(線程獨有的對象)

TSObject存放線程所持有的信息,TSObject實例的方法只會由單線程調用,由TSObjectCollection管理,每個線程都擁有獨立的TSObject實例。(案例中的TSLog類就是TSObject)

?

?

?

?

四、ThreadLocal的原理

JDK中有一個類就實現了Thread-Specific Storage模式,即ThreadLocal,ThreadLocal類主要有四個方法:

1、初始化返回值的方法:
該方法實現只返回 null,并且修飾符為protected,很明顯,如果用戶想返回初始值不為null,則需要重寫該方法;

protected T initialValue() {return null; }

?

2、get方法,獲取線程本地副本變量

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {T result = (T)e.value;return result;}}return setInitialValue(); }

?

3、set方法,設置線程本地副本變量

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }

?

4、remove方法,移除線程本地副本變量

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

?

4.2 實現原理

如果需要我們自己來設計ThreadLocal對象,那么,一般的實現思路:設計一個線程安全的Map,key就是當前線程對象,Value就是線程本地變量的值。

然而,JDK的實現思路:

讓每個Thread對象,自身持有一個Map,這個Map的Key就是當前ThreadLocal對象,Value是本地線程變量值。相對于加鎖的實現方式,這樣做可以提升性能,其實是一種以時間換空間的思路。

?

ThreadLocal類有個getMap()方法,其實就是返回Thread對象自身的Map——threadLocals。

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

set方法如下:

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

?get方法如下:

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

看了源碼就能懂了,假如線程有變量A和變量B,那么變量A會使用ThreadLocalA的set方法存放,變量B會使用ThreadLocalB的set方法存放,而set方法實際上是將變量A存放在線程自己的Map里,key為ThreadLocalA,value是變量A。

?

threadLocals是一種ThreadLocal.ThreadLocalMap類型的數據結構,作為內部類定義在ThreadLocal類中,其內部采用一種WeakReference(弱引用)的方式保存鍵值對。

?

?

Entry繼承了WeakReference:

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

?

?

4.3 使用注意

Hash沖突

ThreadLocalMap中解決Hash沖突采用線性探測的方式。所謂線性探測:

就是根據初始key的hashcode值確定元素在table數組中的位置,如果發現這個位置上已經有其他key值的元素被占用,則利用固定的算法尋找一定步長的下個位置,依次判斷,直至找到能夠存放的位置。

ThreadLocalMap采用線性探測的方式解決Hash沖突的效率很低(簡單地步長+1),所以如果有大量不同的ThreadLocal對象放入map中時發送沖突,則效率很低。

?

使用建議

每個線程只存一個變量,這樣的話所有的線程存放到map中的Key都是相同的ThreadLocal,如果一個線程要保存多個變量,就需要創建多個ThreadLocal,多個ThreadLocal放入Map中時會極大的增加Hash沖突的可能。

?

內存泄漏

ThreadLocal在ThreadLocalMap中是以一個弱引用類型被Entry中的Key引用的,因此如果ThreadLocal沒有外部強引用來引用它,那么ThreadLocal會在下次JVM垃圾收集時被回收。

這個時候就會出現Entry中Key已經被回收,出現一個null Key的情況,外部讀取ThreadLocalMap中的元素是無法通過null Key來找到Value的。

因此如果當前線程的生命周期很長,一直存在,那么其內部的ThreadLocalMap對象也一直生存下來,這些null key就存在一條強引用鏈的關系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,這條強引用鏈會導致Entry不會回收,Value也不會回收,但Entry中的Key卻已經被回收的情況,造成內存泄漏。

?

?

但JVM團隊已經考慮到這樣的情況,并做了一些措施來保證ThreadLocal盡量不會內存泄漏:
在ThreadLocal的get()、set()、remove()方法調用的時候會清除掉線程的ThreadLocalMap中所有Entry中Key為null的Value,并將整個Entry設置為null,利于下次內存回收。

?

最好的解決方案:

每次使用完ThreadLocal,都調用它的remove()方法,清除數據。

/*** Remove the entry for key.*/private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}

?

借鑒學習自https://segmentfault.com/a/1190000015558915

?

總結

以上是生活随笔為你收集整理的(十三)Thread-Specific Storage(ThreadLocal)模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。