hibernate中Session的執行緒安全的討論

iteye_11910發表於2011-08-29
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功能來實現的。

相關文章