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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java map 如何根据key获得对象_ThreadLocal:Java中的影分身

發布時間:2023/12/2 java 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java map 如何根据key获得对象_ThreadLocal:Java中的影分身 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于ThreadLocal,你有哪些疑問?

  • ThreadLocal是用來解決什么問題的?
  • 如何使用ThreadLocal?
  • ThreadLocal的實現原理是什么?
  • 可否舉幾個實際項目中使用ThreadLocal的案例?
  • 基礎知識

    ThreadLocal是線程局部變量,和普通變量的不同在于:每個線程持有這個變量的一個副本,可以獨立修改(set方法)和訪問(get方法)這個變量,并且線程之間不會發生沖突。

    類中定義的ThreadLocal實例一般會被private static修飾,這樣可以讓ThreadLocal實例的狀態和Thread綁定在一起,業務上,一般用ThreadLocal包裝一些業務ID(user ID或事務ID)——不同的線程使用的ID是不相同的。

    如何使用

    case1

    從某個角度來看,ThreadLocal為Java并發編程提供了額外的思路——避免并發,如果某個對象本身是非線程安全的,但是你想實現多線程同步訪問的效果,例如SimpleDateFormat,你可以使用ThreadLocal變量。

    public class Foo {// SimpleDateFormat is not thread-safe, so give one to each threadprivate static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){@Overrideprotected SimpleDateFormat initialValue(){return new SimpleDateFormat("yyyyMMdd HHmm");}}; ?public String formatIt(Date date){return formatter.get().format(date);} }

    注意,這里針對每個線程只需要初始化一次SimpleDateFormat對象,其實跟在自定義線程中定義一個SimpleDateFormat成員變量,并在線程初始化的時候new這個對象,效果是一樣的,只是這樣看起來代碼更規整。

    case2

    之前在yunos做酷盤項目的數據遷移時,我們需要按照用戶維度去加鎖,每個線程在處理遷移之前,都需要先獲取當前用戶的鎖,每個鎖的key是帶著用戶信息的,因此也可以使用ThreadLocal變量實現:

    case3

    下面這個例子,我們定義了一個MyRunnable對象,這個MyRunnable對象會被線程1和線程2使用,但是通過內部的ThreadLocal變量,每個線程訪問到的整數都是自己單獨的一份。

    package org.java.learn.concurrent.threadlocal; ? /*** @author duqi* @createTime 2018-12-29 23:25**/ public class ThreadLocalExample {public static class MyRunnable implements Runnable { ?private ThreadLocal<Integer> threadLocal =new ThreadLocal<Integer>(); ?@Overridepublic void run() {threadLocal.set((int) (Math.random() * 100D)); ?try {Thread.sleep(2000);} catch (InterruptedException e) {} ?System.out.println(threadLocal.get());}} ? ?public static void main(String[] args) throws InterruptedException {MyRunnable sharedRunnableInstance = new MyRunnable(); ?Thread thread1 = new Thread(sharedRunnableInstance);Thread thread2 = new Thread(sharedRunnableInstance); ?thread1.start();thread2.start(); ?thread1.join(); //wait for thread 1 to terminatethread2.join(); //wait for thread 2 to terminate} }

    ThreadLocal關鍵知識點

    源碼分析

    ThreadLocal是如何被線程使用的?原理如下圖所示:Thread引用和ThreadLocal引用都在棧上,Thread引用會引用一個ThreadLocalMap對象,這個map中的key是ThreadLocal對象(使用WeakReference包裝),value是業務上變量的值。

    首先看java.lang.Thread中的代碼:

    public class Thread implements Runnable {//......其他源碼/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null; ?/** InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//......其他源碼

    Thread中的threadLocals變量指向的是一個map,這個map就是ThreadLocal.ThreadLocalMap,里面存放的是跟當前線程綁定的ThreadLocal變量;inheritableThreadLocals的作用相同,里面也是存放的ThreadLocal變量,但是存放的是從當前線程的父線程繼承過來的ThreadLocal變量。

    在看java.lang.ThreadLocal類,主要的成員和接口如下:

  • withInitial方法,Java 8以后用于初始化ThreadLocal的一種方法,在外部調用get()方法的時候,會通過Supplier確定變量的初始值;
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
    }
  • get方法,獲取當前線程的變量副本,如果當前線程還沒有創建該變量的副本,則需要通過調用initialValue方法來設置初始值;get方法的源代碼如下,首先通過當前線程獲取當前線程對應的map,如果map不為空,則從map中取出對應的Entry,然后取出對應的值;如果map為空,則調用setInitialValue設置初始值;如果map不為空,當前ThreadLocal實例對應的Entry為空,則也需要設置初始值。
    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();
    }
  • set方法,跟get方法一樣,先獲取當前線程對應的map,如果map為空,則調用createMap創建map,否則將變量的值放入map——key為當前這個ThreadLocal對象,value為變量的值。
    public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    }
  • remove方法,刪除當前線程綁定的這個副本
    public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
    m.remove(this);
    }
  • 數字0x61c88647,這個值是HASH_INCREMENT的值,普通的hashmap是使用鏈表來處理沖突的,但是ThreadLocalMap是使用線性探測法來處理沖突的,HASH_INCREMENT就是每次增加的步長,根據參考資料1所說,選擇這個數字是為了讓沖突概率最小。
    /**
    * The difference between successively generated hash codes - turns
    * implicit sequential thread-local IDs into near-optimally spread
    * multiplicative hash values for power-of-two-sized tables.
    */
    private static final int HASH_INCREMENT = 0x61c88647;
  • 父子進程數據共享

    InheritableThreadLocal主要用于子線程創建時,需要自動繼承父線程的ThreadLocal變量,實現子線程訪問父線程的threadlocal變量。InheritableThreadLocal繼承了ThreadLocal,并重寫了childValue、getMap、createMap三個方法。

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {/*** 創建線程的時候,如果需要繼承且父線程中Thread-Local變量,則需要將父線程中的ThreadLocal變量一次拷貝過來。*/protected T childValue(T parentValue) {return parentValue;} ?/*** 由于重寫了getMap,所以在操作InheritableThreadLocal變量的時候,將只操作Thread類中的inheritableThreadLocals變量,與threadLocals變量沒有關系**/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;} ?/*** 跟getMap類似,set或getInheritableThreadLocal變量的時候,將只操作Thread類中的inheritableThreadLocals變量*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);} }

    關于childValue多說兩句,拷貝是如何發生的? 首先看Thread.init方法,

    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {//其他源碼if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize; ?/* Set thread ID */tid = nextThreadID();}

    然后看ThreadLocal.createInheritedMap方法,最終會調用到newThreadLocalMap方法,這里InheritableThreadLocal對childValue做了重寫,可以看出,這里確實是將父線程關聯的ThreadLocalMap中的內容依次拷貝到子線程的ThreadLocalMap中了。

    private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len]; ?for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

    ThreadLocal對象何時被回收?

    ThreadLocalMap中的key是ThreadLocal對象,然后ThreadLocal對象時被WeakReference包裝的,這樣當沒有強引用指向該ThreadLocal對象之后,或者說Map中的ThreadLocal對象被判定為弱引用可達時,就會在垃圾收集中被回收掉??聪翬ntry的定義:

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

    ThreadLocal和線程池一起使用?

    ThreadLocal對象的生命周期跟線程的生命周期一樣長,那么如果將ThreadLocal對象和線程池一起使用,就可能會遇到這種情況:一個線程的ThreadLocal對象會和其他線程的ThreadLocal對象串掉,一般不建議將兩者一起使用。

    案例學習

    Dubbo中對ThreadLocal的使用

    我從Dubbo中找到了ThreadLocal的例子,它主要是用在請求緩存的場景,具體代碼如下:

    @Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY) public class CacheFilter implements Filter { ?private CacheFactory cacheFactory; ?public void setCacheFactory(CacheFactory cacheFactory) {this.cacheFactory = cacheFactory;} ?@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation);if (cache != null) {String key = StringUtils.toArgumentString(invocation.getArguments());Object value = cache.get(key);if (value != null) {if (value instanceof ValueWrapper) {return new RpcResult(((ValueWrapper)value).get());} else {return new RpcResult(value);}}Result result = invoker.invoke(invocation);if (!result.hasException()) {cache.put(key, new ValueWrapper(result.getValue()));}return result;}}return invoker.invoke(invocation);}

    可以看出,在RPC調用(invoke)的鏈路上,會先使用請求參數判斷當前線程是否剛剛發起過同樣參數的調用——這個調用會使用ThreadLocalCache保存起來。具體的看,ThreadLocalCache的實現如下:

    package org.apache.dubbo.cache.support.threadlocal; ? import org.apache.dubbo.cache.Cache; import org.apache.dubbo.common.URL; ? import java.util.HashMap; import java.util.Map; ? /*** ThreadLocalCache*/ public class ThreadLocalCache implements Cache { ?//ThreadLocal里存放的是參數到結果的映射private final ThreadLocal<Map<Object, Object>> store; ?public ThreadLocalCache(URL url) {this.store = new ThreadLocal<Map<Object, Object>>() {@Overrideprotected Map<Object, Object> initialValue() {return new HashMap<Object, Object>();}};} ?@Overridepublic void put(Object key, Object value) {store.get().put(key, value);} ?@Overridepublic Object get(Object key) {return store.get().get(key);} ? }

    RocketMQ

    在RocketMQ中,我也找到了ThreadLocal的身影,它是用在消息發送的場景,MQClientAPIImpl是RMQ中負責將消息發送到服務端的實現,其中有一個步驟需要選擇一個具體的隊列,選擇具體的隊列的時候,不同的線程有自己負責的index值,這里使用了ThreadLocal的機制,可以看下ThreadLocalIndex的實現:

    package org.apache.rocketmq.client.common; ? import java.util.Random; ? public class ThreadLocalIndex {private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();private final Random random = new Random(); ?public int getAndIncrement() {Integer index = this.threadLocalIndex.get();if (null == index) {index = Math.abs(random.nextInt());if (index < 0)index = 0;this.threadLocalIndex.set(index);} ?index = Math.abs(index + 1);if (index < 0)index = 0; ?this.threadLocalIndex.set(index);return index;} ?@Overridepublic String toString() {return "ThreadLocalIndex{" +"threadLocalIndex=" + threadLocalIndex.get() +'}';} }

    總結

    這篇文章主要是解決了關于ThreadLocal的幾個問題:(1)具體的概念是啥?(2)在Java開發中的什么場景下使用?(3)ThreadLocal的實現原理是怎樣的?(4)開源項目中有哪些案例可以參考?不知道你是否對這幾個問題有了一定的了解呢?如果還有疑問,歡迎交流。

    參考資料

  • Why 0x61c88647?
  • Java ThreadLocal
  • When and how should I use a ThreadLocal variable?
  • 技術小黑屋:理解Java中的ThreadLocal
  • 深入分析ThreadLocal的內存泄漏問題
  • 《Java并發編程實戰》
  • InheritableThreadLocal詳解
  • ThreadLocal詳解
  • ThreadLocal的使用場景
  • 數據結構:哈希表
  • 看到這里的都是真愛粉,有兩個小小的請求:

  • 如果你覺得本文有所收獲,麻煩右下角點個“好看”;
  • 本號專注于Java后端技術、IT行業雜談、程序員面試等主題,堅持高質量原創產出,如果你對它的內容比較肯定,歡迎掃碼關注哦
  • 總結

    以上是生活随笔為你收集整理的java map 如何根据key获得对象_ThreadLocal:Java中的影分身的全部內容,希望文章能夠幫你解決所遇到的問題。

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