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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

聊一聊Spring中的线程安全性

發(fā)布時(shí)間:2023/11/28 生活经验 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 聊一聊Spring中的线程安全性 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文出處:SylvanasSun

      • Spring與線程安全
      • ThreadLocal
      • ThreadLocal中的內(nèi)存泄漏
      • 參考文獻(xiàn)

Spring與線程安全


Spring作為一個(gè)IOC/DI容器,幫助我們管理了許許多多的“bean”。但其實(shí),Spring并沒(méi)有保證這些對(duì)象的線程安全,需要由開(kāi)發(fā)者自己編寫(xiě)解決線程安全問(wèn)題的代碼。

Spring對(duì)每個(gè)bean提供了一個(gè)scope屬性來(lái)表示該bean的作用域。它是bean的生命周期。例如,一個(gè)scope為singleton的bean,在第一次被注入時(shí),會(huì)創(chuàng)建為一個(gè)單例對(duì)象,該對(duì)象會(huì)一直被復(fù)用到應(yīng)用結(jié)束。

  • singleton:默認(rèn)的scope,每個(gè)scope為singleton的bean都會(huì)被定義為一個(gè)單例對(duì)象,該對(duì)象的生命周期是與Spring IOC容器一致的(但在第一次被注入時(shí)才會(huì)創(chuàng)建)。

  • prototype:bean被定義為在每次注入時(shí)都會(huì)創(chuàng)建一個(gè)新的對(duì)象。

  • request:bean被定義為在每個(gè)HTTP請(qǐng)求中創(chuàng)建一個(gè)單例對(duì)象,也就是說(shuō)在單個(gè)請(qǐng)求中都會(huì)復(fù)用這一個(gè)單例對(duì)象。

  • session:bean被定義為在一個(gè)session的生命周期內(nèi)創(chuàng)建一個(gè)單例對(duì)象。

  • application:bean被定義為在ServletContext的生命周期中復(fù)用一個(gè)單例對(duì)象。

  • websocket:bean被定義為在websocket的生命周期中復(fù)用一個(gè)單例對(duì)象。

我們交由Spring管理的大多數(shù)對(duì)象其實(shí)都是一些無(wú)狀態(tài)的對(duì)象,這種不會(huì)因?yàn)槎嗑€程而導(dǎo)致?tīng)顟B(tài)被破壞的對(duì)象很適合Spring的默認(rèn)scope,每個(gè)單例的無(wú)狀態(tài)對(duì)象都是線程安全的(也可以說(shuō)只要是無(wú)狀態(tài)的對(duì)象,不管單例多例都是線程安全的,不過(guò)單例畢竟節(jié)省了不斷創(chuàng)建對(duì)象與GC的開(kāi)銷(xiāo))。

無(wú)狀態(tài)的對(duì)象即是自身沒(méi)有狀態(tài)的對(duì)象,自然也就不會(huì)因?yàn)槎鄠€(gè)線程的交替調(diào)度而破壞自身狀態(tài)導(dǎo)致線程安全問(wèn)題。無(wú)狀態(tài)對(duì)象包括我們經(jīng)常使用的DO、DTO、VO這些只作為數(shù)據(jù)的實(shí)體模型的貧血對(duì)象,還有Service、DAO和Controller,這些對(duì)象并沒(méi)有自己的狀態(tài),它們只是用來(lái)執(zhí)行某些操作的。例如,每個(gè)DAO提供的函數(shù)都只是對(duì)數(shù)據(jù)庫(kù)的CRUD,而且每個(gè)數(shù)據(jù)庫(kù)Connection都作為函數(shù)的局部變量(局部變量是在用戶棧中的,而且用戶棧本身就是線程私有的內(nèi)存區(qū)域,所以不存在線程安全問(wèn)題),用完即關(guān)(或交還給連接池)。

有人可能會(huì)認(rèn)為,我使用request作用域不就可以避免每個(gè)請(qǐng)求之間的安全問(wèn)題了嗎?這是完全錯(cuò)誤的,因?yàn)镃ontroller默認(rèn)是單例的,一個(gè)HTTP請(qǐng)求是會(huì)被多個(gè)線程執(zhí)行的,這就又回到了線程的安全問(wèn)題。當(dāng)然,你也可以把Controller的scope改成prototype,實(shí)際上Struts2就是這么做的,但有一點(diǎn)要注意,Spring MVC對(duì)請(qǐng)求的攔截粒度是基于每個(gè)方法的,而Struts2是基于每個(gè)類(lèi)的,所以把Controller設(shè)為多例將會(huì)頻繁的創(chuàng)建與回收對(duì)象,嚴(yán)重影響到了性能。

通過(guò)閱讀上文其實(shí)已經(jīng)說(shuō)的很清楚了,Spring根本就沒(méi)有對(duì)bean的多線程安全問(wèn)題做出任何保證與措施。對(duì)于每個(gè)bean的線程安全問(wèn)題,根本原因是每個(gè)bean自身的設(shè)計(jì)。不要在bean中聲明任何有狀態(tài)的實(shí)例變量或類(lèi)變量,如果必須如此,那么就使用ThreadLocal把變量變?yōu)榫€程私有的,如果bean的實(shí)例變量或類(lèi)變量需要在多個(gè)線程之間共享,那么就只能使用synchronized、lock、CAS等這些實(shí)現(xiàn)線程同步的方法了。

下面將通過(guò)解析ThreadLocal的源碼來(lái)了解它的實(shí)現(xiàn)與作用,ThreadLocal是一個(gè)很好用的工具類(lèi),它在某些情況下解決了線程安全問(wèn)題(在變量不需要被多個(gè)線程共享時(shí))。

ThreadLocal


ThreadLocal是一個(gè)為線程提供線程局部變量的工具類(lèi)。它的思想也十分簡(jiǎn)單,就是為線程提供一個(gè)線程私有的變量副本,這樣多個(gè)線程都可以隨意更改自己線程局部的變量,不會(huì)影響到其他線程。不過(guò)需要注意的是,ThreadLocal提供的只是一個(gè)淺拷貝,如果變量是一個(gè)引用類(lèi)型,那么就要考慮它內(nèi)部的狀態(tài)是否會(huì)被改變,想要解決這個(gè)問(wèn)題可以通過(guò)重寫(xiě)ThreadLocal的initialValue()函數(shù)來(lái)自己實(shí)現(xiàn)深拷貝,建議在使用ThreadLocal時(shí)一開(kāi)始就重寫(xiě)該函數(shù)。

ThreadLocal與像synchronized這樣的鎖機(jī)制是不同的。首先,它們的應(yīng)用場(chǎng)景與實(shí)現(xiàn)思路就不一樣,鎖更強(qiáng)調(diào)的是如何同步多個(gè)線程去正確地共享一個(gè)變量,ThreadLocal則是為了解決同一個(gè)變量如何不被多個(gè)線程共享。從性能開(kāi)銷(xiāo)的角度上來(lái)講,如果鎖機(jī)制是用時(shí)間換空間的話,那么ThreadLocal就是用空間換時(shí)間。

ThreadLocal中含有一個(gè)叫做ThreadLocalMap的內(nèi)部類(lèi),該類(lèi)為一個(gè)采用線性探測(cè)法實(shí)現(xiàn)的HashMap。它的key為T(mén)hreadLocal對(duì)象而且還使用了WeakReference,ThreadLocalMap正是用來(lái)存儲(chǔ)變量副本的。

    /*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread.  To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}....}

ThreadLocal中只含有三個(gè)成員變量,這三個(gè)變量都是與ThreadLocalMap的hash策略相關(guān)的。

    /*** ThreadLocals rely on per-thread linear-probe hash maps attached* to each thread (Thread.threadLocals and* inheritableThreadLocals).  The ThreadLocal objects act as keys,* searched via threadLocalHashCode.  This is a custom hash code* (useful only within ThreadLocalMaps) that eliminates collisions* in the common case where consecutively constructed ThreadLocals* are used by the same threads, while remaining well-behaved in* less common cases.*/private final int threadLocalHashCode = nextHashCode();/*** The next hash code to be given out. Updated atomically. Starts at* zero.*/private static AtomicInteger nextHashCode =new AtomicInteger();/*** 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;/*** Returns the next hash code.*/private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}

唯一的實(shí)例變量threadLocalHashCode是用來(lái)進(jìn)行尋址的hashcode,它由函數(shù)nextHashCode()生成,該函數(shù)簡(jiǎn)單地通過(guò)一個(gè)增量HASH_INCREMENT來(lái)生成hashcode。至于為什么這個(gè)增量為0x61c88647,主要是因?yàn)門(mén)hreadLocalMap的初始大小為16,每次擴(kuò)容都會(huì)為原來(lái)的2倍,這樣它的容量永遠(yuǎn)為2的n次方,該增量選為0x61c88647也是為了盡可能均勻地分布,減少碰撞沖突。

        /*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;    /*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}

要獲得當(dāng)前線程私有的變量副本需要調(diào)用get()函數(shù)。首先,它會(huì)調(diào)用getMap()函數(shù)去獲得當(dāng)前線程的ThreadLocalMap,這個(gè)函數(shù)需要接收當(dāng)前線程的實(shí)例作為參數(shù)。如果得到的ThreadLocalMap為null,那么就去調(diào)用setInitialValue()函數(shù)來(lái)進(jìn)行初始化,如果不為null,就通過(guò)map來(lái)獲得變量副本并返回。

setInitialValue()函數(shù)會(huì)去先調(diào)用initialValue()函數(shù)來(lái)生成初始值,該函數(shù)默認(rèn)返回null,我們可以通過(guò)重寫(xiě)這個(gè)函數(shù)來(lái)返回我們想要在ThreadLocal中維護(hù)的變量。之后,去調(diào)用getMap()函數(shù)獲得ThreadLocalMap,如果該map已經(jīng)存在,那么就用新獲得value去覆蓋舊值,否則就調(diào)用createMap()函數(shù)來(lái)創(chuàng)建新的map。

    /*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/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();}/*** Variant of set() to establish initialValue. Used instead* of set() in case user has overridden the set() method.** @return the initial value*/private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}protected T initialValue() {return null;}

ThreadLocal的set()與remove()函數(shù)要比get()的實(shí)現(xiàn)還要簡(jiǎn)單,都只是通過(guò)getMap()來(lái)獲得ThreadLocalMap然后對(duì)其進(jìn)行操作。

    /*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.*/public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}/*** Removes the current thread's value for this thread-local* variable.  If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {@linkplain #set set} by the current thread* in the interim.  This may result in multiple invocations of the* {@code initialValue} method in the current thread.** @since 1.5*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}

getMap()函數(shù)與createMap()函數(shù)的實(shí)現(xiàn)也十分簡(jiǎn)單,但是通過(guò)觀察這兩個(gè)函數(shù)可以發(fā)現(xiàn)一個(gè)秘密:ThreadLocalMap是存放在Thread中的。

    /*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}// Thread中的源碼/* 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;

仔細(xì)想想其實(shí)就能夠理解這種設(shè)計(jì)的思想。有一種普遍的方法是通過(guò)一個(gè)全局的線程安全的Map來(lái)存儲(chǔ)各個(gè)線程的變量副本,但是這種做法已經(jīng)完全違背了ThreadLocal的本意,設(shè)計(jì)ThreadLocal的初衷就是為了避免多個(gè)線程去并發(fā)訪問(wèn)同一個(gè)對(duì)象,盡管它是線程安全的。而在每個(gè)Thread中存放與它關(guān)聯(lián)的ThreadLocalMap是完全符合ThreadLocal的思想的,當(dāng)想要對(duì)線程局部變量進(jìn)行操作時(shí),只需要把Thread作為key來(lái)獲得Thread中的ThreadLocalMap即可。這種設(shè)計(jì)相比采用一個(gè)全局Map的方法會(huì)多占用很多內(nèi)存空間,但也因此不需要額外的采取鎖等線程同步方法而節(jié)省了時(shí)間上的消耗。

ThreadLocal中的內(nèi)存泄漏


我們要考慮一種會(huì)發(fā)生內(nèi)存泄漏的情況,如果ThreadLocal被設(shè)置為null后,而且沒(méi)有任何強(qiáng)引用指向它,根據(jù)垃圾回收的可達(dá)性分析算法,ThreadLocal將會(huì)被回收。這樣一來(lái),ThreadLocalMap中就會(huì)含有key為null的Entry,而且ThreadLocalMap是在Thread中的,只要線程遲遲不結(jié)束,這些無(wú)法訪問(wèn)到的value會(huì)形成內(nèi)存泄漏。為了解決這個(gè)問(wèn)題,ThreadLocalMap中的getEntry()、set()和remove()函數(shù)都會(huì)清理key為null的Entry,以下面的getEntry()函數(shù)的源碼為例。

        /*** Get the entry associated with key.  This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss.  This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param  key the thread local object* @return the entry associated with key, or null if no such*/private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}/*** Version of getEntry method for use when key is not found in* its direct hash slot.** @param  key the thread local object* @param  i the table index for key's hash code* @param  e the entry at table[i]* @return the entry associated with key, or null if no such*/private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;// 清理key為null的Entrywhile (e != null) {ThreadLocal<?> k = e.get();if (k == key)return e;if (k == null)expungeStaleEntry(i);elsei = nextIndex(i, len);e = tab[i];}return null;}

在上文中我們發(fā)現(xiàn)了ThreadLocalMap的key是一個(gè)弱引用,那么為什么使用弱引用呢?使用強(qiáng)引用key與弱引用key的差別如下:

  • 強(qiáng)引用key:ThreadLocal被設(shè)置為null,由于ThreadLocalMap持有ThreadLocal的強(qiáng)引用,如果不手動(dòng)刪除,那么ThreadLocal將不會(huì)回收,產(chǎn)生內(nèi)存泄漏。

  • 弱引用key:ThreadLocal被設(shè)置為null,由于ThreadLocalMap持有ThreadLocal的弱引用,即便不手動(dòng)刪除,ThreadLocal仍會(huì)被回收,ThreadLocalMap在之后調(diào)用set()、getEntry()和remove()函數(shù)時(shí)會(huì)清除所有key為null的Entry。

但要注意的是,ThreadLocalMap僅僅含有這些被動(dòng)措施來(lái)補(bǔ)救內(nèi)存泄漏問(wèn)題。如果你在之后沒(méi)有調(diào)用ThreadLocalMap的set()、getEntry()和remove()函數(shù)的話,那么仍然會(huì)存在內(nèi)存泄漏問(wèn)題。

在使用線程池的情況下,如果不及時(shí)進(jìn)行清理,內(nèi)存泄漏問(wèn)題事小,甚至還會(huì)產(chǎn)生程序邏輯上的問(wèn)題。所以,為了安全地使用ThreadLocal,必須要像每次使用完鎖就解鎖一樣,在每次使用完ThreadLocal后都要調(diào)用remove()來(lái)清理無(wú)用的Entry。

參考文獻(xiàn)


  • Are Spring objects thread safe? - Stack Overflow

  • Spring Singleton, Request, Session Beans and Thread Safety | Java Enterprise Ecosystem.

  • Spring Framework Documentation

總結(jié)

以上是生活随笔為你收集整理的聊一聊Spring中的线程安全性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。