來源:SylvanasSun ,
juejin.im/post/5a0045ef5188254de169968e
Spring與線程安全
Spring做爲一個IOC/DI容器,幫助咱們管理了許許多多的「bean」。但其實,Spring並無保證這些對象的線程安全,須要由開發者本身編寫解決線程安全問題的代碼。
Spring對每一個bean提供了一個scope屬性來表示該bean的做用域。它是bean的生命週期。例如,一個scope爲singleton的bean,在第一次被注入時,會建立爲一個單例對象,該對象會一直被複用到應用結束。
咱們交由Spring管理的大多數對象其實都是一些無狀態的對象,這種不會由於多線程而致使狀態被破壞的對象很適合Spring的默認scope,每一個單例的無狀態對象都是線程安全的(也能夠說只要是無狀態的對象,無論單例多例都是線程安全的,不過單例畢竟節省了不斷建立對象與GC的開銷)。
無狀態的對象便是自身沒有狀態的對象,天然也就不會由於多個線程的交替調度而破壞自身狀態致使線程安全問題。無狀態對象包括咱們常用的DO、DTO、VO這些只做爲數據的實體模型的貧血對象,還有Service、DAO和Controller,這些對象並無本身的狀態,它們只是用來執行某些操做的。例如,每一個DAO提供的函數都只是對數據庫的CRUD,並且每一個數據庫Connection都做爲函數的局部變量(局部變量是在用戶棧中的,並且用戶棧自己就是線程私有的內存區域,因此不存在線程安全問題),用完即關(或交還給鏈接池)。
有人可能會認爲,我使用request做用域不就能夠避免每一個請求之間的安全問題了嗎?這是徹底錯誤的,由於Controller默認是單例的,一個controller對象是會被多個線程共享的,這就又回到了線程的安全問題。固然,你也能夠把Controller的scope改爲prototype,實際上Struts2就是這麼作的,但有一點要注意,Spring MVC對請求的攔截粒度是基於每一個方法的,而Struts2是基於每一個類的,因此把Controller設爲多例將會頻繁的建立與回收對象,嚴重影響到了性能。
經過閱讀上文其實已經說的很清楚了,Spring根本就沒有對bean的多線程安全問題作出任何保證與措施。對於每一個bean的線程安全問題,根本緣由是每一個bean自身的設計。不要在bean中聲明任何有狀態的實例變量或類變量,若是必須如此,那麼就使用ThreadLocal把變量變爲線程私有的,若是bean的實例變量或類變量須要在多個線程之間共享,那麼就只能使用synchronized、lock、CAS等這些實現線程同步的方法了。
下面將經過解析ThreadLocal的源碼來了解它的實現與做用,ThreadLocal是一個很好用的工具類,它在某些狀況下解決了線程安全問題(在變量不須要被多個線程共享時)。
ThreadLocal
ThreadLocal是一個爲線程提供線程局部變量的工具類。它的思想也十分簡單,就是爲線程提供一個線程私有的變量副本,這樣多個線程均可以隨意更改本身線程局部的變量,不會影響到其餘線程。不過須要注意的是,ThreadLocal提供的只是一個淺拷貝,若是變量是一個引用類型,那麼就要考慮它內部的狀態是否會被改變,想要解決這個問題能夠經過重寫ThreadLocal的initialValue()函數來本身實現深拷貝,建議在使用ThreadLocal時一開始就重寫該函數。
ThreadLocal與像synchronized這樣的鎖機制是不一樣的。首先,它們的應用場景與實現思路就不同,鎖更強調的是如何同步多個線程去正確地共享一個變量,ThreadLocal則是爲了解決同一個變量如何不被多個線程共享。從性能開銷的角度上來說,若是鎖機制是用時間換空間的話,那麼ThreadLocal就是用空間換時間。
ThreadLocal中含有一個叫作ThreadLocalMap的內部類,該類爲一個採用線性探測法實現的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策略相關的。
/**
* 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是用來進行尋址的hashcode,它由函數nextHashCode()生成,該函數簡單地經過一個增量HASH_INCREMENT來生成hashcode。至於爲何這個增量爲0x61c88647,主要是由於ThreadLocalMap的初始大小爲16,每次擴容都會爲原來的2倍,這樣它的容量永遠爲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);
要得到當前線程私有的變量副本須要調用get()函數。首先,它會調用getMap()函數去得到當前線程的ThreadLocalMap,這個函數須要接收當前線程的實例做爲參數。若是獲得的ThreadLocalMap爲null,那麼就去調用setInitialValue()函數來進行初始化,若是不爲null,就經過map來得到變量副本並返回。
setInitialValue()函數會去先調用initialValue()函數來生成初始值,該函數默認返回null,咱們能夠經過重寫這個函數來返回咱們想要在ThreadLocal中維護的變量。以後,去調用getMap()函數得到ThreadLocalMap,若是該map已經存在,那麼就用新得到value去覆蓋舊值,不然就調用createMap()函數來建立新的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()函數要比get()的實現還要簡單,都只是經過getMap()來得到ThreadLocalMap而後對其進行操做。
/**
* 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()函數與createMap()函數的實現也十分簡單,可是經過觀察這兩個函數能夠發現一個祕密: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;
仔細想一想其實就可以理解這種設計的思想。有一種廣泛的方法是經過一個全局的線程安全的Map來存儲各個線程的變量副本,可是這種作法已經徹底違背了ThreadLocal的本意,設計ThreadLocal的初衷就是爲了不多個線程去併發訪問同一個對象,儘管它是線程安全的。而在每一個Thread中存放與它關聯的ThreadLocalMap是徹底符合ThreadLocal的思想的,當想要對線程局部變量進行操做時,只須要把Thread做爲key來得到Thread中的ThreadLocalMap便可。這種設計相比採用一個全局Map的方法會多佔用不少內存空間,但也所以不須要額外的採起鎖等線程同步方法而節省了時間上的消耗。
ThreadLocal中的內存泄漏
咱們要考慮一種會發生內存泄漏的狀況,若是ThreadLocal被設置爲null後,並且沒有任何強引用指向它,根據垃圾回收的可達性分析算法,ThreadLocal將會被回收。這樣一來,ThreadLocalMap中就會含有key爲null的Entry,並且ThreadLocalMap是在Thread中的,只要線程遲遲不結束,這些沒法訪問到的value會造成內存泄漏。爲了解決這個問題,ThreadLocalMap中的getEntry()、set()和remove()函數都會清理key爲null的Entry,如下面的getEntry()函數的源碼爲例。
/**
* 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;
在上文中咱們發現了ThreadLocalMap的key是一個弱引用,那麼爲何使用弱引用呢?使用強引用key與弱引用key的差異以下:
但要注意的是,ThreadLocalMap僅僅含有這些被動措施來補救內存泄漏問題。若是你在以後沒有調用ThreadLocalMap的set()、getEntry()和remove()函數的話,那麼仍然會存在內存泄漏問題。
在使用線程池的狀況下,若是不及時進行清理,內存泄漏問題事小,甚至還會產生程序邏輯上的問題。因此,爲了安全地使用ThreadLocal,必需要像每次使用完鎖就解鎖同樣,在每次使用完ThreadLocal後都要調用remove()來清理無用的Entry
spring和springMVC的面試問題總結
2017年08月30日 10:44:25 一頁知秋否 閱讀數 112249更多
版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
1.Spring中AOP的應用場景、Aop原理、好處?
答:AOP--Aspect Oriented Programming面向切面編程;用來封裝橫切關注點,具體能夠在下面的場景中使用:
Authentication 權限、Caching 緩存、Context passing 內容傳遞、Error handling 錯誤處理Lazy loading懶加載、Debugging調試、logging, tracing, profiling and monitoring 記錄跟蹤優化 校準、Performance optimization 性能優化、Persistence 持久化、Resource pooling 資源池、Synchronization 同步、Transactions 事務
原理:AOP是面向切面編程,是經過動態代理的方式爲程序添加統一功能,集中解決一些公共問題。
優勢:1.各個步驟之間的良好隔離性耦合性大大下降
2.源代碼無關性,再擴展功能的同時不對源碼進行修改操做
2.Spring中IOC的做用與原理?對象建立的過程。
答:IOC--Inversion of Control控制反轉。當某個角色須要另一個角色協助的時候,在傳統的程序設計過程當中,一般由調用者來建立被調用者的實例對象。但在spring中建立被調用者的工做再也不由調用者來完成,所以稱爲控制反轉。建立被調用者的工做由spring來完成,而後注入調用者 直接使用。
3.介紹spring框架
它是一個一站式(full-stack全棧式)框架,提供了從表現層-springMVC到業務層-spring再到持久層-springdata的一套完整的解決方案。咱們在項目中能夠只使用spring一個框架,它就能夠提供表現層的mvc框架,持久層的Dao框架。它的兩大核心IoC和AOP更是爲咱們程序解耦和代碼簡潔易維護提供了支持。
4.Spring常見建立對象的註解?
答:@Component@Controller@ Service@ Repository
5.Spring中用到的設計模式
答:簡單工廠、工廠方法、單例模式、適配器、包裝器、代理、觀察者、策略、模板方法
詳細介紹:請參考本微博的:開發經常使用設計模式
6.Spring的優勢?
答:1.下降了組件之間的耦合性 ,實現了軟件各層之間的解耦
2.可使用容易提供的衆多服務,如事務管理,消息服務等
3.容器提供單例模式支持
4.容器提供了AOP技術,利用它很容易實現如權限攔截,運行期監控等功能
5.容器提供了衆多的輔助類,能加快應用的開發
6.spring對於主流的應用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring屬於低侵入式設計,代碼的污染極低
8.獨立於各類應用服務器
9.spring的DI機制下降了業務對象替換的複雜性
10.Spring的高度開放性,並不強制應用徹底依賴於Spring,開發者能夠自由選擇spring 的部分或所有
7.Spring Bean的做用域之間有什麼區別?
Spring容器中的bean能夠分爲5個範圍。全部範圍的名稱都是自說明的,可是爲了不混淆,仍是讓咱們來解釋一下:
singleton:這種bean範圍是默認的,這種範圍確保無論接受到多少個請求,每一個容器中只有一個bean的實例,單例的模式由bean factory自身來維護。
prototype:原形範圍與單例範圍相反,爲每個bean請求提供一個實例。
request:在請求bean範圍內會每個來自客戶端的網絡請求建立一個實例,在請求完成之後,bean會失效並被垃圾回收器回收。
Session:與請求範圍相似,確保每一個session中有一個bean的實例,在session過時後,bean會隨之失效。
global-session:global-session和Portlet應用相關。當你的應用部署在Portlet容器中工做時,它包含不少portlet。若是你想要聲明讓全部的portlet共用全局的存儲變量的話,那麼這全局變量須要存儲在global-session中。
全局做用域與Servlet中的session做用域效果相同。
8.Spring管理事務有幾種方式?
答:有兩種方式:
一、編程式事務,在代碼中硬編碼。(不推薦使用)
二、聲明式事務,在配置文件中配置(推薦使用)
聲明式事務又分爲兩種:
a、基於XML的聲明式事務
b、基於註解的聲明式事務
9.spring中自動裝配的方式有哪些?
答:一、 No:即不啓用自動裝配。
二、 byName:經過屬性的名字的方式查找JavaBean依賴的對象併爲其注入。好比說類Computer有個屬性printer,指定其autowire屬性爲byName後,Spring IoC容器會在配置文件中查找id/name屬性爲printer的bean,而後使用Seter方法爲其注入。
三、 byType:經過屬性的類型查找JavaBean依賴的對象併爲其注入。好比類Computer有個屬性printer,類型爲Printer,那麼,指定其autowire屬性爲byType後,Spring IoC容器會查找Class屬性爲Printer的bean,使用Seter方法爲其注入。
四、 constructor:通byType同樣,也是經過類型查找依賴對象。與byType的區別在於它不是使用Seter方法注入,而是使用構造子注入。
五、 autodetect:在byType和constructor之間自動的選擇注入方式。
六、 default:由上級標籤<beans>的default-autowire屬性肯定。
10.spring中的核心類有那些,各有什麼做用?
答:BeanFactory:產生一個新的實例,能夠實現單例模式
BeanWrapper:提供統一的get及set方法
ApplicationContext:提供框架的實現,包括BeanFactory的全部功能
11.Bean的調用方式有哪些?
答:有三種方式能夠獲得Bean並進行調用:
一、使用BeanWrapper
HelloWorld hw=new HelloWorld();
BeanWrapper bw=new BeanWrapperImpl(hw);
bw.setPropertyvalue(」msg」,」HelloWorld」);
system.out.println(bw.getPropertyCalue(」msg」));
二、使用BeanFactory
InputStream is=new FileInputStream(」config.xml」);
XmlBeanFactory factory=new XmlBeanFactory(is);
HelloWorld hw=(HelloWorld) factory.getBean(」HelloWorld」);
system.out.println(hw.getMsg());
三、使用ApplicationConttext
ApplicationContext actx=new FleSystemXmlApplicationContext(」config.xml」);
HelloWorld hw=(HelloWorld) actx.getBean(」HelloWorld」);
System.out.println(hw.getMsg());
12.什麼是IOC,什麼又是DI,他們有什麼區別?
答:依賴注入DI是一個程序設計模式和架構模型, 一些時候也稱做控制反轉,儘管在技術上來說,依賴注入是一個IOC的特殊實現,依賴注入是指一個對象應用另一個對象來提供一個特殊的能力,例如:把一個 數據庫鏈接已參數的形式傳到一個對象的結構方法裏面而不是在那個對象內部自行建立一個鏈接。控制反轉和依賴注入的基本思想就是把類的依賴從類內部轉化到外 部以減小依賴
應用控制反轉,對象在被建立的時候,由一個調控系統內全部對象的外界實體,將其所依賴的對象的引用,傳遞給它。也能夠說,依賴被注入到對象中。所 以,控制反轉是,關於一個對象如何獲取他所依賴的對象的引用,這個責任的反轉。
13.spring有兩種代理方式:
答: 若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
優勢:由於有接口,因此使系統更加鬆耦合
缺點:爲每個目標類建立接口
若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。
優勢:由於代理類與目標類是繼承關係,因此不須要有接口的存在。
缺點:由於沒有使用接口,因此係統的耦合性沒有使用JDK的動態代理好。
14.springMVC的流程?
答:1.用戶發送請求至前端控制器DispatcherServlet
2.DispatcherServlet收到請求調用HandlerMapping處理器映射器。
3.處理器映射器根據請求url找到具體的處理器,生成處理器對象及處理器攔截器(若是有則生成)一併返回給DispatcherServlet。
4.DispatcherServlet經過HandlerAdapter處理器適配器調用處理器
5.執行處理器(Controller,也叫後端控制器)。
6.Controller執行完成返回ModelAndView
7.HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet
8.DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
9.ViewReslover解析後返回具體View
10.DispatcherServlet對View進行渲染視圖(即將模型數據填充至視圖中)。
11.DispatcherServlet響應用戶
![](http://static.javashuo.com/static/loading.gif)
15.Springmvc的優勢
答:1.它是基於組件技術的.所有的應用對象,不管控制器和視圖,仍是業務對象之類的都是 java組件.而且和Spring提供的其餘基礎結構緊密集成.
2.不依賴於Servlet API(目標雖是如此,可是在實現的時候確實是依賴於Servlet的)
3. 能夠任意使用各類視圖技術,而不只僅侷限於JSP
4 . 支持各類請求資源的映射策略
5 .它應是易於擴展的