亚洲全黄无码一级在线看_国产剧情久久久性色_无码av一区二区三区无码_亚洲成a×人片在线观看

當(dāng)前位置: 首頁 > 科技新聞 >

聊一聊 Spring 中的線程安全性

時間:2020-01-19 15:32來源:網(wǎng)絡(luò)整理 瀏覽:
Spring與線程安全 Spring作為一個IOC/DI容器,幫助我們管理了許許多多的“bean”。但其實,Spring并沒有保證這些對

Spring與線程安全

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

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

singleton:默認(rèn)的scope,每個scope為singleton的bean都會被定義為一個單例對象,該對象的生命周期是與Spring IOC容器一致的(但在第一次被注入時才會創(chuàng)建)。 prototype:bean被定義為在每次注入時都會創(chuàng)建一個新的對象。 request:bean被定義為在每個HTTP請求中創(chuàng)建一個單例對象,也就是說在單個請求中都會復(fù)用這一個單例對象。 session:bean被定義為在一個session的生命周期內(nèi)創(chuàng)建一個單例對象。 application:bean被定義為在ServletContext的生命周期中復(fù)用一個單例對象。 websocket:bean被定義為在websocket的生命周期中復(fù)用一個單例對象。

我們交由Spring管理的大多數(shù)對象其實都是一些無狀態(tài)的對象,這種不會因為多線程而導(dǎo)致狀態(tài)被破壞的對象很適合Spring的默認(rèn)scope,每個單例的無狀態(tài)對象都是線程安全的(也可以說只要是無狀態(tài)的對象,不管單例多例都是線程安全的,不過單例畢竟節(jié)省了不斷創(chuàng)建對象與GC的開銷)。

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

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

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

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

ThreadLocal

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

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

ThreadLocal中含有一個叫做ThreadLocalMap的內(nèi)部類,該類為一個采用線性探測法實現(xiàn)的HashMap。它的key為ThreadLocal對象而且還使用了WeakReference,ThreadLocalMap正是用來存儲變量副本的。

/**

* 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中只含有三個成員變量,這三個變量都是與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);

}

唯一的實例變量threadLocalHashCode是用來進(jìn)行尋址的hashcode,它由函數(shù)nextHashCode()生成,該函數(shù)簡單地通過一個增量HASH_INCREMENT來生成hashcode。至于為什么這個增量為0x61c88647,主要是因為ThreadLocalMap的初始大小為16,每次擴容都會為原來的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ù)。首先,它會調(diào)用getMap()函數(shù)去獲得當(dāng)前線程的ThreadLocalMap,這個函數(shù)需要接收當(dāng)前線程的實例作為參數(shù)。如果得到的ThreadLocalMap為null,那么就去調(diào)用setInitialValue()函數(shù)來進(jìn)行初始化,如果不為null,就通過map來獲得變量副本并返回。

setInitialValue()函數(shù)會去先調(diào)用initialValue()函數(shù)來生成初始值,該函數(shù)默認(rèn)返回null,我們可以通過重寫這個函數(shù)來返回我們想要在ThreadLocal中維護(hù)的變量。之后,去調(diào)用getMap()函數(shù)獲得ThreadLocalMap,如果該map已經(jīng)存在,那么就用新獲得value去覆蓋舊值,否則就調(diào)用createMap()函數(shù)來創(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);

else

createMap(t, value);

return value;

}

protected T initialValue() {

return null;

}

ThreadLocal的set()與remove()函數(shù)要比get()的實現(xiàn)還要簡單,都只是通過getMap()來獲得ThreadLocalMap然后對其進(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);

else

createMap(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ù)的實現(xiàn)也十分簡單,但是通過觀察這兩個函數(shù)可以發(fā)現(xiàn)一個秘密: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è)計的思想。有一種普遍的方法是通過一個全局的線程安全的Map來存儲各個線程的變量副本,但是這種做法已經(jīng)完全違背了ThreadLocal的本意,設(shè)計ThreadLocal的初衷就是為了避免多個線程去并發(fā)訪問同一個對象,盡管它是線程安全的。而在每個Thread中存放與它關(guān)聯(lián)的ThreadLocalMap是完全符合ThreadLocal的思想的,當(dāng)想要對線程局部變量進(jìn)行操作時,只需要把Thread作為key來獲得Thread中的ThreadLocalMap即可。這種設(shè)計相比采用一個全局Map的方法會多占用很多內(nèi)存空間,但也因此不需要額外的采取鎖等線程同步方法而節(jié)省了時間上的消耗。

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

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

else

return 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的Entry

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;

}

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

強引用key:ThreadLocal被設(shè)置為null,由于ThreadLocalMap持有ThreadLocal的強引用,如果不手動刪除,那么ThreadLocal將不會回收,產(chǎn)生內(nèi)存泄漏。 弱引用key:ThreadLocal被設(shè)置為null,由于ThreadLocalMap持有ThreadLocal的弱引用,即便不手動刪除,ThreadLocal仍會被回收,ThreadLocalMap在之后調(diào)用set()、getEntry()和remove()函數(shù)時會清除所有key為null的Entry。

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

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

責(zé)任編輯:

推薦內(nèi)容