SessionFactory和Session的線程安全的討論

Session session = SessionFactory.getSession();
這一步加final的意思是session這個引用對象只能指向SessionFactort.getSession()傳回的這個對象,之後其指向的對象地址不能再次改變,不加final是可以再次賦值(即再次改變其指向)的。而加不加final,session所指向的這個對象的內在屬性是完全可以改變的,甚至可以模擬兩個線程,同時調用其方法,改變這個對象的設置。所以加final與線程安全與否幾乎沒有關係。
方法中的局部變量會隨着方法的調用而新建和釋放,因此沒有線程安全之憂,有擔憂的確是是類變量,不過這也要看情況。類的實例對象在正常使用的情況下也不存在線程問題(不同線程不同對象),但某些情況就會有問題,比如servletClass的實例,請求第一次到達後web容器會實例化對應的servletClass,而此後無論再有多少類似請求,都會使用第一次實例化的這個對象(除非長期沒有這個請求,web容器有可能會銷燬這個對象)而http請求是有並發現象存在的,操作的又是同一個對象,當然會出現併發操作同一個對象中的同一個類變量的問題,這就是線程不安全的一個例子。
SessionFactory在方法中創建Session,並返回給調用端,當然不存在線程問題,當然能保證爲不同地點,不同線程的調用者提供不同的Session,而Session一旦創建,就要看調用者如何使用了,把它當做類變量使用,而又把這個類的實例供多個線程操作,而又不加排它鎖,當然會出線程安全的問題。通常在控制單元(servletClass/struts的action)使用hibernateSession或jdbc的connection時,都不建議把它作爲類變量來用

 

 

“在不使用ThreadLocal維護session的情況下。爲什麼說Session是非線程安全的。是否是因爲它存在很多類級變量嗎?如果確實如此的話,那麼SessionFactory儘管是是線程安全的。但它同樣也存在很多類級的非final成員實例變量,如:private transient boolean isClosed = false;private transient SchemaExport schemaExport;這難道不會影響線程安全麼?”
 
這個問題應該從兩個角度說:
首先,我認爲樓主需要弄清楚的是final關鍵字可以修飾多種目標,final可以修飾非抽象類、非抽象類成員方法和變量。
如果是修飾類,那麼final類不能被繼承,沒有子類,final類中的方法默認是final的。
如果是修飾方法,那麼final方法不能被子類的方法覆蓋,但可以被繼承。
如果是修飾成員變量,那麼final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
另外,final不能用於修飾構造方法。
由此可見,SF(SessionFactory)中的確已經將絕大部分的成員變量設計成了final的,因此,確實不存在併發衝突的問題,而對僅有的幾個非final成員變量的操作也並非在SF類中提供,因此,SF確實是現成安全的。反觀Session,transient是和序列化相關的一個修飾符,和線程安全無關,因此,Session中的大部分成員變量都復發避免並非訪問衝突的可能。因此,Session確實不能認爲是線程安全的。

其次,線程安全和線程敏感這兩個概念我個人認爲應該區分一下,ThreadLocal實現的只是一種線程敏感特性,也就是說,Hibernate2中需要用Util包中的方法獲得和線程相關的Session,而到了Hibernate3,儘管SessionFactory中已經整合好了ThreadLocal的特性,這主要是依靠CurrentSessionContext接口的不同實現類(如JPA,Hibernate等實現模式)來從一個大的SessionMap中獲得和線程相關的Session(這種Session一般稱爲線程敏感的),但是,Session自身依然是非線程安全的,原因見前面“首先”部分的分析。這裏引入“其次”的分析主要是想避免將線程安全和線程敏感混淆。

最後,10樓的哥們第一段和最後一段說的很多,第一段中說的final的位置樓主不要認爲是修飾成員變量,他說的final的位置是修飾整個實例對象,這個時候由於實例對象屬於引用對象,因此考慮到堆內存和棧內存的關係,實際對象依然可以被不同線程改變,因此,在實例對象前加final修飾符並不能代表這個對象就是線程安全的。
10樓的最後一段說的也很好,其實之所以說要認識到Session是非線程安全的,主要是提醒大家,在自己開發的調用類中,不要將Session設置爲成員變量,而是要將Session寫在方法中,這樣得到的Session纔是因線程而異的,如果放在類中,只隨着Servlet初始化時構建一次,那麼今後併發衝突時肯定難免的了。

 

isClose()確實被SF實現類提供,是暴露的,但是,不是說一個類暴露了變量,那麼這個類就一定是不安全的,這裏所謂的安全主要是指是否能有效控制併發。而所謂併發控制首先需要明確的是邊界,併發控制的邊界我認爲是:"從你執行‘讀’到你執行‘寫’這一過程中有沒有其它的人‘寫入’?" 由於SF的open與close基本上是託管給容器的,因此這個變量的狀態不是一個線程可以改變的,所以,這樣的變量並不涉及併發衝突。因此也不影響線程安全。
另外,併發控制一般靠鎖來實現,他的核心思想是解決‘讀’完之後誰能‘寫’,而事物隔離級別解決的是‘寫’完之後誰能‘讀’。他們分別體現的是ACID中的C和I,是不同的概念。有沒有其它的人寫?”
綜上,一個暴露的變量如果從功能設計上的目的只是爲了提供‘讀取’的目的,那麼即使暴露也不會產生任何預期的併發衝突。

 

 

SessionFactoryImpl 顯然不是嚴格語義上的thread safe,那麼多getXXX()都暴露在外面,客戶程序可以隨意put。
爲什麼java doc裏面說的“It is crucial that the class is not only thread safe, but also highly concurrent.” ?
那是因爲有些變量雖然可以改,但是不會影響功能,因爲在constructor的時候變量已經從Configuration裏面解析好了,譬如:SessionFactoryImpl.setting。
再者有些變量已經處理過了,你get了也沒法改。譬如:SessionFactoryImpl.classMetadata,在constructor的時候已經classMetadata = Collections.unmodifiableMap(classMeta);了

也就是說,‘寫入’這個功能不是線程提供的,而是構造方法,託管容器,甚至Aspect功能來實現的。

發佈了72 篇原創文章 · 獲贊 4 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章