#
Tomcat5集群中的SESSION復(fù)制
Tomcat 5服務(wù)器為集群和SESSION復(fù)制提供了集成的支持。本系列的第一篇文章將為大家提供SESSION持久性以及TOMCAT集群中SESSION復(fù)制的內(nèi)在工作機(jī)制一個概要認(rèn)識。我將會討論SESSION復(fù)制在TOMCAT5中是怎樣進(jìn)行的以及跨越多集群節(jié)點的SESSION持久性的復(fù)制機(jī)制。在第2部分,我會詳細(xì)討論一個帶有SESSION復(fù)制功能的TOMCAT集群的安裝例子,并且比較不同的復(fù)制情形。
集群
傳統(tǒng)獨立服務(wù)器(非集群的)不提供任何失效無縫轉(zhuǎn)移以及負(fù)載平衡能力。當(dāng)服務(wù)器失敗的時候,就無法獲取整個網(wǎng)站的內(nèi)容,除非服務(wù)器被重新喚起。由于服務(wù)器失效,任何存儲在服務(wù)器內(nèi)存中的SESSION都會丟失,用戶必須重新登陸并且輸入所有由于服務(wù)器失效丟失的數(shù)據(jù)。
不同的是,作為集群一部分的服務(wù)器則提供了可測性以及失效無縫轉(zhuǎn)移能力。一個集群就是一組同步運行并且協(xié)同工作,能提供高可靠性,高穩(wěn)定性以及高可測性的多服務(wù)器例程。服務(wù)端集群對客戶端表現(xiàn)出來似乎就是一個單獨的服務(wù)器例程。從客戶端的視角來看,集群的客戶端和單獨的服務(wù)器沒多大不同,但是他們通過提供實效無縫轉(zhuǎn)移和SESSION復(fù)制做到了不間斷服務(wù)以及SESSION數(shù)據(jù)持久性。
集群中的服務(wù)器通訊
集群中的應(yīng)用程序服務(wù)器通過諸如IP多點傳送(IP multicast)和IP sockets這樣的技術(shù)和其他服務(wù)器共享信息
●IP多點傳送:主要用于1對多的服務(wù)器通訊,通過廣播服務(wù)和 heartbeats消息的可用來顯示服務(wù)器的有效
●IP sockets:主要用于在集群的服務(wù)器例程中進(jìn)行P2P服務(wù)器通訊
使用ip多點傳送進(jìn)行一對多通訊
TOMCAT服務(wù)器使用IP多點傳送在集群中的服務(wù)器例程間進(jìn)行一對多的通訊,IP多點傳送是一種能夠讓多服務(wù)器向指定IP地址和端口號進(jìn)行訂閱并且監(jiān)聽消息的廣播技術(shù)(多點傳送IP地址范圍從224.0.0.0 到239.255.255.255)。在集群中的每個服務(wù)器都使用多點傳送廣播特定的 heartbeat消息,通過監(jiān)視這些 heartbeat消息,在集群中的服務(wù)器例程判斷什么時候服務(wù)器例程失效。在服務(wù)器通訊中使用IP多點傳送的一個缺點是他不能保證這些消息被確實接收到了。例如,一個應(yīng)用持續(xù)的本地多點傳送緩存滿了,就不能寫入新的多點傳送消息,等消息過了之后該應(yīng)用程序就沒有被通知到。
使用ip Sockets進(jìn)行服務(wù)器通訊
IP sockets 同樣也通過了一套在集群中的服務(wù)器間進(jìn)行發(fā)送消息和數(shù)據(jù)的機(jī)制。服務(wù)器例程使用IP sockets 在集群節(jié)點間進(jìn)行HTTP SESSION狀態(tài)的復(fù)制。正確的SOKET配制對于集群的性能是至關(guān)重要的,基于SOCKET的通訊的效率取決于SOCKET的實現(xiàn)類別(例如:系統(tǒng)使用本地的或者純JAVA SOCKET讀取器實現(xiàn)),如果服務(wù)器使用純JAVA SOCKET讀取器則要看服務(wù)器例程是否注冊使用了足夠的SOCKET讀取器線程。
如果想要有最佳的SOCKET性能,系統(tǒng)應(yīng)該注冊使用本地的SOCEKT而不是純JAVA實現(xiàn)。這是因為相對于基于JAVA的SOCKET實現(xiàn),本地SOCKET消耗更少的系統(tǒng)資源。雖然SOCKET讀取器的JAVA實現(xiàn)是P2P通信中一種可靠而且可移動的方法,可是他不能為集群中的重型SOCKET使用提供最好的性能。當(dāng)判斷從SOCKET是否有數(shù)據(jù)讀取的時候本地SOCKET讀取器使用了更有效率的方法。使用本地SOCKET讀取器實現(xiàn),讀取器線程不需要去統(tǒng)計靜止的SOCKET:他們僅僅為活動的SOCKET服務(wù),并且在一個給定的SOCKET開始活躍起來時他們可以立刻捕捉到。而使用純JAVA SOCKET讀取器,線程必須動態(tài)的統(tǒng)計所有打開的SOCKET,判斷他們是否包含可讀取的數(shù)據(jù)。換句話說,SOCKET讀取器總是忙于統(tǒng)計SOCKET,即使這些SOCKET沒有數(shù)據(jù)可讀。這些本不應(yīng)該的系統(tǒng)開銷降低了性能。
TOMCAT 5中的集群
雖然在TOMCAT5的早些版本中也有集群的功能,但是在稍后的版本中(5.0.19或者更高),集群變的更加模塊組件化。在 server.XML 中集群元素已經(jīng)被重構(gòu),這樣我們可以替換集群的不同部分而不會影響其他元素。例如,當(dāng)前配置中把成員服務(wù)設(shè)置為多點傳送發(fā)現(xiàn)。這里可以輕易地把成員服務(wù)修改替換為使用TCP或者 Unicast ,而不會改變集類邏輯的其他部分。
其他一些集群元素,例如SESSION管理器,復(fù)制發(fā)送端,復(fù)制接受端也可以被自定義的實現(xiàn)取代而不影響集群配置的其他部分。同樣,在TOMCAT集群中的任何服務(wù)器組件可以使用集類API向集群中的所有成員發(fā)送消息。
SESSION復(fù)制
服務(wù)器集群通常操縱兩種SESSION: sticky sessions和 replicated sessions .sticky sessions就是存在單機(jī)服務(wù)器中的接受網(wǎng)絡(luò)請求的SESSION,其他集群成員對該服務(wù)器的SESSION狀態(tài)完全不清楚,如果存有SESSION的服務(wù)器失敗的話,用戶必須再次登陸網(wǎng)站,重新輸入所有存儲在SESSION中的數(shù)據(jù)。
另一種SESSION類型是,在一臺服務(wù)器中SESSION狀態(tài)被復(fù)制到集群中的其他所有服務(wù)器上,無論何時,只要SESSION 被改變,SESSION數(shù)據(jù)都要重新被復(fù)制。這就是 replicated session . sticky 和 replicated sessions都有他們的優(yōu)缺點, Sticky sessions簡單而又容易操作,因為我們不必復(fù)制任何SESSION數(shù)據(jù)到其他服務(wù)器上。這樣就會減少系統(tǒng)消耗,提高性能。但是如果服務(wù)器失敗,所有存儲在該服務(wù)器內(nèi)存中的SESSION數(shù)據(jù)也同樣會消失。如果SESSION數(shù)據(jù)沒有被復(fù)制到其他服務(wù)器,這些SESSION就完全丟失了。當(dāng)我們在進(jìn)行一個查詢事務(wù)當(dāng)中的時候,丟失所有已經(jīng)輸入的數(shù)據(jù),就會導(dǎo)致很多問題。
為了支持 jsp HTTP session 狀態(tài)的自動失效無縫轉(zhuǎn)移,TOMCAT服務(wù)器復(fù)制了在內(nèi)存中的SESSION狀態(tài)。這是通過復(fù)制存儲在一臺服務(wù)器上的SESSION數(shù)據(jù)到集群中其他成員上防止數(shù)據(jù)丟失以及允許失效無縫轉(zhuǎn)移。
對象的狀態(tài)管理
通過在服務(wù)器上的保存狀態(tài)可以區(qū)分出4種對象:
●無狀態(tài):一個無狀態(tài)對象在調(diào)用的時候不會在內(nèi)存中保存任何狀態(tài),因為客戶端和服務(wù)器端沒必要保存任何有關(guān)對方的信息。在這種情況下,客戶端會在每次請求服務(wù)器時都會發(fā)送數(shù)據(jù)給服務(wù)器。SESSION狀態(tài)被在客戶端和服務(wù)器端來回發(fā)送。這種方法不總是可行和理想的,特別是當(dāng)傳輸?shù)臄?shù)據(jù)比較大或者一些安全信息我們不想保存在客戶端的時候;
●會話:一個會話對象在一個SESSION中只被用于特定的某個客戶端。在SESSION中,他可以為所有來自該客戶端的請求服務(wù),并且僅僅是這個客戶端的請求。貫穿一個SESSION,兩個請求間的狀態(tài)信息必須保存。會話服務(wù)通常在內(nèi)存中保存短暫的狀態(tài),當(dāng)在服務(wù)器失敗的時候可能會丟失。SESSION狀態(tài)通常被保存在請求間的服務(wù)器的內(nèi)存中。為了清空內(nèi)存,SESSION狀態(tài)也可以被從內(nèi)存中釋放(就像在一個對象CACHE)。在該對象中,性能和可量測性都有待提高,因為更新并不是被單獨的寫到磁盤上,并且服務(wù)器失敗的時候數(shù)據(jù)也沒辦法搶救。
●緩存:緩存對象在內(nèi)存中保存狀態(tài),并且使用這個去處理從多客戶端來的請求。緩存服務(wù)的實現(xiàn)可以擴(kuò)展到他們把緩存的是數(shù)據(jù)備份保存在后端存儲器中(通常是一個關(guān)系數(shù)據(jù)庫)。
●獨立的:一個獨立的對象在一個時間內(nèi)只活躍在集群中的一臺服務(wù)器上,處理來自多客戶端的請求。他通常由那些私有的,持久的,在內(nèi)存中緩寸的數(shù)據(jù)支持。他同樣也在內(nèi)存中保持短暫狀態(tài),在服務(wù)器失敗的時候要重建或者丟失。當(dāng)失敗的時候,獨立對象必須在同一個服務(wù)器上重起或者移植到另一臺服務(wù)器上。
SESSION復(fù)制的設(shè)計考慮事項
網(wǎng)絡(luò)考慮事項
把集群的多點傳送地址和其他應(yīng)用程序隔離是至關(guān)重要的。我們不希望集群配置或者網(wǎng)絡(luò)布局干擾到多點傳送服務(wù)器通信。和其他應(yīng)用程序共享集群多點傳送地址將迫使集群的服務(wù)器例程處理不應(yīng)該的消息,消耗系統(tǒng)內(nèi)存。共享多點傳送地址可能也會使IP多點傳送緩沖過載,延遲服務(wù)器 heartbeat 消息傳輸。這樣的延遲可能導(dǎo)致一個服務(wù)器例程被標(biāo)識為死亡,僅僅因為他的 heartbeat 消息沒有被及時接收。
編程考慮事項
除了上面提到的網(wǎng)絡(luò)相關(guān)因素,還有些和我們寫 J2EE 網(wǎng)絡(luò)應(yīng)用程序有關(guān)的設(shè)計考慮也會影響SESSION復(fù)制。以下列出了一些編程方面的考慮:
●SESSION數(shù)據(jù)必須被序列化:為了支持HTTP session 狀態(tài)的內(nèi)存內(nèi)復(fù)制,所有的 servlet 和 JSP session 數(shù)據(jù)必須被序列化,對象中的每個域都必須被序列化,這樣對象被可靠的序列化。
●把應(yīng)用程序設(shè)計為冪等的:冪等的的意思就是一個操做不會修改狀態(tài)信息,并且每次操作的時候都返回同樣的結(jié)果(換句話說就是:做多次和做一次的效果是一樣的),通常,WEB請求,特別是 HTML forms 都被發(fā)送多次(當(dāng)用戶點擊發(fā)送按紐兩次,重載頁面多次),導(dǎo)致多次HTTP請求。設(shè)計SERVLET和其他WEB對象為 冪等的,可以容忍多次請求。詳細(xì)可以去參考設(shè)計模式“Synchronized Token ”和“Idempotent Receiver ”關(guān)于怎樣設(shè)計冪等的的應(yīng)用程序。
●在BUSINESS層存儲狀態(tài):會話狀態(tài)應(yīng)該使用有狀態(tài)的SESSION BEANS存儲在EJB層,而不是存儲在WEB層的HttpSession.因為企業(yè)應(yīng)用程序要支持各種類型客戶端(WEB客戶端,JAVA應(yīng)用程序,其他EJB),存儲數(shù)據(jù)在WEB層會導(dǎo)致在客戶端的雙數(shù)據(jù)存儲。因此,有狀態(tài)的SESSION BEAN在這些情況下就被用于存儲SESSION狀態(tài)。無狀態(tài)的SESSION BEAN要為每次的調(diào)用重構(gòu)造會話狀態(tài)。這些狀態(tài)可能必須從數(shù)據(jù)庫中恢復(fù)的數(shù)據(jù)中重編譯。這些缺點失去了使用無狀態(tài)SESSION BEAN去提高性能和可測量性的目的,嚴(yán)重的減低了性能。
●序列化系統(tǒng)消耗:序列化SESSION數(shù)據(jù)在復(fù)制SESSION狀態(tài)的時候回會些系統(tǒng)消耗。隨著序列化對象大小的增長消耗也越多。最好是保持SESSION容量適當(dāng)?shù)男 5侨绻惚仨氃赟ESSION中創(chuàng)建非常大的對象,最好測試下你的 servlets 性能以保證性能是可接受的以及SESSION的復(fù)制時間是適當(dāng)?shù)摹?/p>
●用戶SESSION:判斷在集群中每個TOMCAT服務(wù)器例程所控制的并發(fā)用戶SESSION最大數(shù)是很重要的。為了控制更多并發(fā)SESSION,我們應(yīng)該為提高效率添加更多內(nèi)存。最大并發(fā)客戶端數(shù),以及每個客戶端請求的頻率在決定SESSION復(fù)制對服務(wù)器的性能影響方面也是個因素。
Tomcat 5中的SESSION復(fù)制
在版本5之前,TOMCAT服務(wù)器只支持sticky sessions (使用mod_jk模塊進(jìn)行負(fù)載平衡)。如果我們需要SESSION復(fù)制,必須依靠第3方軟件例如JavaGroups 去實現(xiàn)。
Tomcat 5服務(wù)器帶有SESSION復(fù)制功能。和集群特征類似,只要修改 server.xml 注冊文件就能實現(xiàn)SESSION復(fù)制。
Martin Fowler 在他的書《 Enterprise Patterns》中談到三個SESSION狀態(tài)持久性模式,這些模式包括:
1.客戶端SESSION狀態(tài):在客戶端存儲SESSION狀態(tài)
2.服務(wù)端SESSION狀態(tài):在一個序列化的FORM中保持SESSION狀態(tài)到一個服務(wù)器系統(tǒng)上。
3.數(shù)據(jù)庫SESSION狀態(tài):當(dāng)在數(shù)據(jù)庫中提交數(shù)據(jù)的時候存儲SESSION數(shù)據(jù)。
TOMCAT支持以下三種SESSION持久性類型:
1.內(nèi)存復(fù)制:在JVM內(nèi)存中復(fù)制SESSION狀態(tài),使用TOMCAT 5安裝帶的SimpleTcpCluster 和 SimpleTcpClusterManager 類。這些類在包org.apache.catalina.cluster中,是server/lib/catalina-cluster.jar的一部分。
2.數(shù)據(jù)庫持久性:在這種類型中,SESSION狀態(tài)保存在一個關(guān)系數(shù)據(jù)庫中,服務(wù)器使用JDBCManager類從數(shù)據(jù)庫中獲取SESSION信息。這個類在包org.apache.catalina.session.JDBCStore中,是catalina.jar的一部分。
3.基于文件的持久性:這里使用類PersistenceManager把SESSION狀態(tài)保存到一個文件系統(tǒng)。這個類在包org.apache.catalina.session.FileStore中,是catalina.jar的一部分。
TOMCAT集群元素以及SESSION復(fù)制這章簡要介紹一下組成TOMCAT集群的元素以及SESSION復(fù)制。
集群:
這個是集群中的主要元素, SimpleTcpCluster類代表這個元素。他使用在server.xml中指定的管理類為所有可分配的web contexts生成ClusterManager.
集群管理器
這個類關(guān)注跨越集群中所有節(jié)點間的SESSION數(shù)據(jù)復(fù)制。所有在web.xml文件中指定了distributable標(biāo)記的WEB應(yīng)用程序都會有SESSION復(fù)制。集群管理器作為集群元素的managerClassName屬性在server.xml中指定。集群管理器的代碼主要被設(shè)計用來分離集群中的元素。我們所要做的就是寫一個SESSION管理器類去實現(xiàn)ClusterManager接口。這樣能讓我們靈活的使用客戶集群管理器而不會影響到集群中的其他元素。這里有兩個復(fù)制算法。SimpleTcpReplicationManager每次復(fù)制全部的SESSION,而DeltaManager只復(fù)制SESSION增量。
最簡單的復(fù)制管理器在每次HTTP請求都復(fù)制所有的SESSION.在SESSION較小的時候這樣是很有用的,我們可以只用以下代碼:
HashMap map = session.getAttribute("map");map.put("data","data");
這里,我們不需要特別調(diào)用session.setAttribute() 或者 removeAttribute方法去復(fù)制SESSION變化。對于每次HTTP請求,在SESSION中的所有屬性都被復(fù)制。可以使用一個叫做useDirtyFlag的屬性去最優(yōu)化SESSION被復(fù)制的次數(shù)。如果這個標(biāo)記被設(shè)為真,我們必須調(diào)用setAttribute()方法去獲取被復(fù)制的SESSION變化。如果被設(shè)為假,每次請求后SESSION都被復(fù)制。
SimpleTcpReplicationManager生成ReplicatedSession執(zhí)行SESSION復(fù)制工作。
提供增量管理器僅僅是為了性能的考慮。它在每次請求的時候都做一次復(fù)制。同時他也調(diào)用監(jiān)聽器,所以如果我們調(diào)用session.setAttribute(),那么在其他服務(wù)器上的監(jiān)聽器就會被調(diào)用。DeltaManager生成DeltaSession執(zhí)行 SESSION復(fù)制。
成員成員的建立通過由TOMCAT例程在同樣的多點傳送IP和端口發(fā)送廣播消息。廣播的消息包括服務(wù)器的IP地址和TCP監(jiān)聽端口(默認(rèn)IP地址為228.0.0.4)。
如果在給定的時間框架內(nèi)一個例程沒有接收到消息(由集群配置中的mcastDropTime參數(shù)指定),成員被認(rèn)為死亡。這個元素由McastService類表示。
由mcastXXX開始的屬性用于成員資格多點傳送PING.以下表格列出了用于IP多點傳送服務(wù)器通訊的屬性。
發(fā)送端
這個元素由ReplicationTransmitter類代表。當(dāng)多點傳送廣播消息被接收到,成員被添加到機(jī)群。在下次復(fù)制請求前,發(fā)送例程將使用主機(jī)和端口信息建立一個TCP SOCKET.使用這些SOCKET發(fā)送序列化的數(shù)據(jù)。在TOMCAT 5中有3種不同的方法操縱SESSION復(fù)制:異步,同步,池復(fù)制模式。以下部分解釋了這些模式怎樣工作,以及他們將被使用在什么情況下。
●異步:在這種復(fù)制模式中,每個集群節(jié)點都有一個單線程扮演SESSION數(shù)據(jù)傳送器。這里,請求線程會把復(fù)制請求發(fā)送到一個隊列,然后返回給客戶端。在失效無縫轉(zhuǎn)移前,如果我們擁有sticky sessions,就應(yīng)該使用異步復(fù)制。這里復(fù)制時間不是至關(guān)重要的,但是請求時間卻是。在異步復(fù)制期間,請求在數(shù)據(jù)被復(fù)制完之前就返回了。這種復(fù)制模式縮短了請求時間。當(dāng)每個請求被分的更開這種模式是很有用的。(例如,在WEB請求間有更長的延遲)。同樣,如果我們不關(guān)心SESSION是否完成復(fù)制這個也很有用,當(dāng)SESSION很較小時, SESSION復(fù)制時間也更短。
●同步:在這種模式中,一個單線程執(zhí)行了HTTP請求和數(shù)據(jù)復(fù)制。集群中所有節(jié)點都接收到SESSION數(shù)據(jù)后線程才返回。同步意味被被復(fù)制的數(shù)據(jù)通過一個單SOCKET發(fā)送。由于使用一個單線程,同步模式可能會是簇性能的一個潛在的瓶頸。這種復(fù)制模式保證了在請求返回前SESSION 已經(jīng)被復(fù)制。
●池:TOMCAT5 在使用池復(fù)制模式進(jìn)行SESSION復(fù)制的方法上提供了很大的改進(jìn)。
池模式基本上是同步模式的一個擴(kuò)展版本。他基于的一個原則是:劃分服務(wù)到多例程,每個例程處理不同的SESSION數(shù)據(jù)片段。同時對接收服務(wù)器開放多SOCKET進(jìn)行發(fā)送SESSION信息。這種方法比通過單SOCKET發(fā)送所有東西更快。因此,使用一個SOCKET池同步復(fù)制SESSION.直到所有SESSION數(shù)據(jù)都復(fù)制完請求才返回。為了有效使用這種模式要增加TCP線程。由于使用多SOCKET,池模式使得集群性能的逐步提高以及更好的可測性。同樣這種模式也是最安全的配置,因為它有足夠數(shù)量的SOCKET在理想的時間內(nèi)發(fā)送所有的SESSION數(shù)據(jù)到其他節(jié)點上。而使用單SOCKET,SESSION數(shù)據(jù)在通過集群時可能丟失或者只是部分傳輸。
接收端
這個集群元素由類ReplicationListener表示。在集群配置中以tcpXXX開始的屬性用于TCP SESSION復(fù)制。以下表格列出了用于配置服務(wù)器復(fù)制中基于SOCEKT服務(wù)器通訊的屬性。
復(fù)制值
復(fù)制值用于判斷哪些HTTP請求需要被復(fù)制。由于我們不經(jīng)常復(fù)制靜態(tài)內(nèi)容(例如HTML和javascript, stylesheets,圖像文件),我們可以使用復(fù)制值元素過濾掉靜態(tài)內(nèi)容。這個值可以用于找出什么時候請求已完成以及初始化復(fù)制。
部署器
部署器元素可以用于部署集群范圍的應(yīng)用程序。通常,部署只部署/解除部署簇內(nèi)的工作成員。所以在損壞的節(jié)點在啟動時沒有WARS的復(fù)制。當(dāng)watchEnabled="true"時配置器為WAR文件監(jiān)視一個目錄(watchDir)。當(dāng)添加一個新的WAR文件時,WAR被部署到本地例程,然后被部署到集群中的其他例程。當(dāng)一個WAR文件從watchDir刪除,這個WAR被從本地和集群范圍內(nèi)解除部署。
所有在TOMCAT集群結(jié)構(gòu)中的元素以及他們的層次關(guān)系都在列在圖1中
圖 1. Tomcat 集群等級結(jié)構(gòu)圖。單擊看原圖。
TOMCAT中SESSION復(fù)制是怎么工作的
以下部分簡要解釋當(dāng)TOMCAT服務(wù)器啟動或則關(guān)閉時集群節(jié)點怎樣分享SESSION信息,詳細(xì)信息可參考Tomcat 5 Clustering文擋。
TC-01:集群中第一個節(jié)點TC-02:集群中第2個節(jié)點
●服務(wù)器啟動:TC-01使用標(biāo)準(zhǔn)服務(wù)器啟動隊列啟動。當(dāng)主機(jī)對象被創(chuàng)建,即有一個集群對象和它相關(guān)聯(lián)。當(dāng)contexts被解析,如果distributable已經(jīng)在web.xml中指定,那么TOMCAT為WEB CONTEXT創(chuàng)建SESSION管理器(SimpleTcpReplicationManager 取代StandardManager)。集群將會啟動一個成員服務(wù)(成員的一個例程)和一個復(fù)制服務(wù)。
當(dāng)TC-02啟動,他也遵循第一個成員(TC-01)同樣的隊列但是有一個不同。集群被啟動并且創(chuàng)建一個成員關(guān)系(TC-01,TC-02)。TC-02將向TC-01請求SESSION狀態(tài)。TC-01回應(yīng)該請求,在TC-2開始監(jiān)聽HTTP請求前,TC-01發(fā)送狀態(tài)給TC-02.如果TC-01不回應(yīng),TC-02將在60秒后進(jìn)入中止?fàn)顟B(tài)并且發(fā)布一個日志入口。SESSIONG 狀態(tài)發(fā)送給所有在web.xml中指定了distributable的WEB應(yīng)用程序。
●創(chuàng)建SESSION:當(dāng)TC-01接收到請求,一個SESSION(S1)被創(chuàng)建,處理進(jìn)入TC-01的請求和沒有SESSION復(fù)制時是一樣的。當(dāng)請求完成時會有以下事件:ReplicationValve將會在回應(yīng)返回給用戶前截取請求。這里,會發(fā)現(xiàn)SESSION已經(jīng)被改變,使用TCP復(fù)制SESSION到TC-02.●服務(wù)器儲運損耗/關(guān)閉:當(dāng)在集群中的一臺服務(wù)器失敗,維護(hù)損壞或者系統(tǒng)升級,其他節(jié)點會受到第一個節(jié)點已經(jīng)脫離集群的通知。TC-02從他的成員資格列刪除TC-01,并且TC-02在也不會收到有關(guān)TC-01任何變動的通知。負(fù)載平衡將會移至TC-02,所有的SESSION由TC-02控制。
當(dāng)TC-01開始恢復(fù),他再次遵循在服務(wù)器開始階段描述的啟動隊列。加入到簇中并且以所有SESSIONG的當(dāng)前狀態(tài)和TC-02通訊。一旦接收到 SESSIONG狀態(tài),也就完成了加載然后打開它的HTTP/ mod_jk端口。所以,要等到從TC-2接受到SESSION狀態(tài)TC-01才能發(fā)送請求。
●SESSION終止:如果在第一個節(jié)點的一個SESSION已經(jīng)無效或則由于過期終止,無效請求將被截取,SESSION會被同其他無效SESSION放在一個隊列中。當(dāng)請求完成,服務(wù)器發(fā)送SESSIONG終止消息給TC-02而不是發(fā)送已經(jīng)改變的SESSION,TC-02同樣也會把該SESSION置無效。我們可以從服務(wù)器控制臺看到SESSIONG無效的消息。無效SESSION在集群中將不會被復(fù)制,直到其他請求傳出系統(tǒng)并且檢查無效隊列。
我的一個客戶不知道該選用Struts還是JSF。就像你預(yù)料的那樣,我通常會問:這2中框架之間有什么區(qū)別?當(dāng)然,除了我的這個客戶外很多人都面臨這樣的選擇。
總的來說,我建議在新項目中優(yōu)先考慮JSF。雖然常常有一些商業(yè)上的因素迫使我們?yōu)楝F(xiàn)有的項目選擇了Struts,而且那些解決方案還有待考驗,但是,讓我們面對一個事實:JSF比Struts好多了。
下面是我選擇JSF而不選Struts的十大理由:
1.Components(組件)
2.Render Kits
3.Renderers
4.Value Binding Expressions(值綁定表達(dá)式)
5.Event Model(事件模型)
6.Extensibility(可擴(kuò)展性)
7.Managed Beans(Dependency Injection 依賴注入)
8.POJO Action Methods
9.JSF is the standard Java-based web app framework (JSF是java web應(yīng)用程序的標(biāo)準(zhǔn)框架)
10.There's only one Struts(只有一個Struts)
10.There's only one Struts(只有一個Struts)
Struts是一個開源產(chǎn)品,然而JSF是一個標(biāo)準(zhǔn)。這個細(xì)節(jié)常常被新的JSF學(xué)習(xí)者忽略,其實這是顯而易見的,因為我們有多個JSF的實現(xiàn)。雖然JSF還很不成熟,但是我們已經(jīng)有了2個優(yōu)秀的JSF實現(xiàn)可以選擇:Sun的參考實現(xiàn)和Apache的MyFaces。另一方面,我們只有一個Struts。
9.JSF is the standard(JSF是標(biāo)準(zhǔn))
JEE 5.0要提供一個JSF的實現(xiàn),這表明JSF不久將會無處不在。這可能與你無關(guān),但是和工具供應(yīng)商密切相關(guān)。現(xiàn)在大概有50個java web應(yīng)用程序框架,工具供應(yīng)商不會情愿去支持一個特別的框架,但是他們會毫不猶豫的去支持一個衛(wèi)星電視器材標(biāo)準(zhǔn)。而且不止供應(yīng)商,開源項目也會迅速的聚集在JSF的四周,爭先恐后的去實現(xiàn)相同的功能。比如說,直到我們?nèi)崿F(xiàn)本質(zhì)上和Shale的Tapestry差不多的視圖的時候,我才知道Facalets。(從長遠(yuǎn)來看,我相信這種冗余是件好事,會給我們帶來好處)
8.POJO Action Methods
Struts的行為是和Struts的API綁定在一起的,但是JSF的行為方法可以在POJPO中實現(xiàn)。這意味著你不用在表單和模型對象之間實現(xiàn)一個多余的行為層。順便說一下,在JSF里面沒有行為對象,行為在模型對象中實現(xiàn)。但是也請注意一點:如果你愿意你也可以生成與JSF獨立的行為對象。在Struts里面,你有Form. Bean和Action Bean。Form. Bean包含數(shù)據(jù)而Action Bean包含邏輯。OO狂會想去合并前2者,在Struts你辦不到。但是在JSF中,你可以分開數(shù)據(jù)和邏輯,也可以合并到一個對象中,一切由你決定。
7.Managed Beans(Dependency Injection 依賴注入)
和Spring一樣,JSF也使用了依賴注入(DJ)(或控制反轉(zhuǎn)(IoC))去實例化和初始化Bean。Struts的確為你生成了Form. Bean和Action Bean,但是JSF可以為你生成各種各樣的Managed Bean。
6.Extensibility(可擴(kuò)展性)
這個很重要。JSF有6個對象實現(xiàn)了這個框架的大部分功能,而且你可以很容易的用你自己的實現(xiàn)代替原有實現(xiàn)。比如你想加一個自定義參數(shù)在JSF表達(dá)式語言里面,或是添加一個自己的視圖控制器以便于區(qū)分組件和HTML。事實上Shale實現(xiàn)了上面的功能。如果你還沒有滿足,JSF提供了幾個地方你可以輕松的控制JSF的生命周期。Shale給你的會更多。
5.Event Model(事件模型)
JSF的事件模型使你可以對值改變,動作,JSF生命周期階段變換等作出反應(yīng)。在JSF1.1中,那些事件都是在服務(wù)器端處理的,這肯定是一個缺陷,好在JSF2.0計劃支持客戶端事件,拭目以待吧。
4.Value Binding Expressions(值綁定表達(dá)式)
在Struts中,你負(fù)責(zé)把數(shù)據(jù)從Form傳遞到模型對象。你實現(xiàn)的Action的execute方法是把Form作為一個參數(shù)。然后你再手動的把數(shù)據(jù)從Form. Bean里面取出放到模型對象里面。你要為應(yīng)用里面的每個Form做這些事情,然而在JSF里面,你只需像這樣:#{model.property} 就夠了,其他的交給JSF來處理。
3.Renderers
你有看過Struts的標(biāo)簽的源代碼嗎?它直接生成HTML。JSF組件標(biāo)簽什么都不生成,它和服務(wù)器上的一對component-renderer對應(yīng)。Component維護(hù)組件狀態(tài),rendered負(fù)責(zé)獲得視圖。重點是renderers是可插拔的,即你可以根據(jù)自己需求實現(xiàn)然后干洗加盟替代掉默認(rèn)實現(xiàn)。比如說我在NFJS上面的Felix談話中舉例說明了怎么去實現(xiàn)一個自定義的label renderer。你只需要配置你的renderer,JSF就會自動在你的應(yīng)用程序里面使用他。
2.Render Kits
在幾年前我曾經(jīng)有份Struts咨詢工作,我們必須同時支持瀏覽器和無線設(shè)備,非常痛苦。但是用JSF來完成那個任務(wù)非常容易,因為你可以生成你自己的render kit-為一種特定顯示技術(shù)的renderers的集合-然后配置到JSF里面。
1.Components(組件)
組件是Struts和JSF之間最大的區(qū)別。就像Swing一樣,JSF提供豐富的底層構(gòu)件去開發(fā)組件然后添加到標(biāo)準(zhǔn)的組件集。那些底層構(gòu)件讓你很容易的生成自己的組件并且和別人共享。現(xiàn)在我們到處都能看到自定義組件跳出來,比如說Oracle的ADF和MyFaces,兩者都提供了豐富的組件集,就像javascript日歷,tree等等。當(dāng)然,組件只是一部分。典型的是,組件都和一個獨立的renderer對應(yīng),這給我們帶來了真正的好處(看第3條)。但是和JSF中的很多東西一樣,你不一定要墨守成規(guī)。只要你愿意,你可以實現(xiàn)render自己的組件,雖然這樣你會失去給組件加入別的renderer的能力。
定義線程安全性
明確定義線程安全性出人意料地困難,大多數(shù)定義看上去完全是自我循環(huán)。快速搜索一下 Google,可以找到以下關(guān)于線程安全代碼的典型的、但是沒有多大幫助的定義(或者可以說是描述):
...可以從多個編程線程中調(diào)用,無需線程之間不必要的交互。
...可以同時被多個線程調(diào)用,不需要調(diào)用一方有任何操作。
有這樣的定義,就不奇怪我們對于線程安全性會感到如此迷惑。這些定義比說“一個類在可以被多個線程安全調(diào)用時就是線程安全的”好不了多少,當(dāng)然,它的意義就是如此,但是它不能幫助我們區(qū)分一個線程安全的類與一個線程不安全的類。安全的意義是什么呢?
實際上,所有線程安全的定義都有某種程序的循環(huán),因為它必須符合類的規(guī)格說明 -- 這是對類的功能、其副作用、哪些狀態(tài)是有效和無效的、不可變量、前置條件、后置條件等等的一種非正式的松散描述(由規(guī)格說明給出的對象狀態(tài)約束只應(yīng)用于外部可見的狀態(tài),即那些可以通過調(diào)用其公共方法和訪問其公共字段看到的狀態(tài),而不應(yīng)用于其私有字段中表示的內(nèi)部狀態(tài))。
線程安全性
類要成為線程安全的,首先必須在單線程環(huán)境中有正確的行為。如果一個類實現(xiàn)正確(這是說它符合規(guī)格說明的另一種方式),那么沒有一種對這個類的對象的操作序列(讀或者寫公共字段以及調(diào)用公共方法)可以讓對象處于無效狀態(tài),觀察到對象處于無效狀態(tài)、或者違反類的任何不可變量、前置條件或者后置條件的情況。
此外,一個類要成為線程安全的,在被多個線程訪問時,不管運行時環(huán)境執(zhí)行這些線程有什么樣的時序安排或者交錯,它必須仍然有如上所述的正確行為,并且在調(diào)用的代碼中沒有任何額外的同步。其效果就是,在所有線程看來,對于線程安全對象的操作是以固定的、全局一致的順序發(fā)生的。
正確性與線程安全性之間的關(guān)系非常類似于在描述 ACID(原子性、一致性、獨立性和持久性)事務(wù)時使用的一致性與獨立性之間的關(guān)系:從特定線程的角度看,由不同線程所執(zhí)行的對象操作是先后(雖然順序不定)而不是并行執(zhí)行的。
方法之問的狀態(tài)依賴
考慮下面的代碼片段,它迭代一個 Vector 中的元素。盡管 Vector 的所有方法都是同步的,但是在多線程的環(huán)境中不做額外的同步就使用這段代碼仍然是不安全的,因為如果另一個線程恰好在錯誤的時間里刪除了一個元素,則 get() 會拋出一個 ArrayIndexOutOfBoundsException 。
1 Vector v = new Vector();
2 // contains race conditions -- may require external synchronization
3 for (int i=0; i<v.size(); i++) {
4 doSomething(v.get(i));
5 }
6
這里發(fā)生的事情是: get(index) 的規(guī)格說明里有一條前置條件要求 index 必須是非負(fù)的并且小于 size() 。但是,在多線程環(huán)境中,沒有辦法可以知道上一次查到的 size() 值是否仍然有效,因而不能確定 i
更明確地說,這一問題是由 get() 的前置條件是以 size() 的結(jié)果來定義的這一事實所帶來的。只要看到這種必須使用一種方法的結(jié)果作為另一種講法的輸入條件的樣式,它就是一個 狀態(tài)依賴,就必須保證干洗設(shè)備至少在調(diào)用這兩種方法期間元素的狀態(tài)沒有改變。一般來說,做到這一點的唯一方法在調(diào)用第一個方法之前是獨占性地鎖定對象,一直到調(diào)用了后一種方法以后。在上面的迭代 Vector 元素的例子中,您需要在迭代過程中同步 Vector 對象。
線程安全程度
如上面的例子所示,線程安全性不是一個非真即假的命題。 Vector 的方法都是同步的,并且 Vector 明確地設(shè)計為在多線程環(huán)境中工作。但是它的線程安全性是有限制的,即在某些方法之間有狀態(tài)依賴(類似地,如果在迭代過程中 Vector 被其他線程修改,那么由 Vector.iterator() 返回的 iterator 會拋出 ConcurrentModificationException )。
對于 Java 類中常見的線程安全性級別,沒有一種分類系統(tǒng)可被廣泛接受,不過重要的是在編寫類時盡量記錄下它們的線程安全行為。
Bloch 給出了描述五類線程安全性的分類方法:不可變、線程安全、有條件線程安全、線程兼容和線程對立。只要明確地記錄下線程安全特性,那么您是否使用這種系統(tǒng)都沒關(guān)系。這種系統(tǒng)有其局限性 -- 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 -- 但是這套系統(tǒng)是一個很好的起點。這種分類系統(tǒng)的核心是調(diào)用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節(jié)分別描述了線程安全性的這五種類別。
不可變
本欄目的普通讀者聽到我贊美不可變性的優(yōu)點時不會感到意外。不可變的對象一定是線程安全的,并且永遠(yuǎn)也不需要額外的同步。因為一個不可變的對象只要構(gòu)建正確,其外部可見狀態(tài)永遠(yuǎn)也不會改變,永遠(yuǎn)也不會看到它處于不一致的狀態(tài)。Java 類庫中大多數(shù)基本數(shù)值類如 Integer 、 String 和 BigInteger 都是不可變的。
線程安全
線程安全的對象具有在上面“線程安全”一節(jié)中描述的屬性 -- 由類的規(guī)格說明所規(guī)定的約束在對象被多個線程訪問時仍然有效,不管運行時環(huán)境如何排列,線程都不需要任何額外的同步。這種線程安全性保證是很嚴(yán)格的 -- 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴(yán)格的定義。
有條件的線程安全
我們在 7 月份的文件“ 并發(fā)集合類”中討論了有條件的線程安全。有條件的線程安全類對于單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器 -- 由這些類返回的 fail-fast 迭代器假定在迭代器進(jìn)行遍歷的時候底層集合不會有變化。為了保證其他線程不會在遍歷的時候改變集合,進(jìn)行迭代的線程應(yīng)該確保它是獨占性地訪問集合以實現(xiàn)遍歷的完整性。通常,獨占性的訪問是由對鎖的干洗同步保證的 -- 并且類的文檔應(yīng)該說明是哪個鎖(通常是對象的內(nèi)部監(jiān)視器(intrinsic monitor))。
如果對一個有條件線程安全類進(jìn)行記錄,那么您應(yīng)該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的并發(fā)訪問。用戶可以合理地假設(shè)其他操作序列不需要任何額外的同步。
線程兼容
線程兼容類不是線程安全的,但是可以通過正確使用同步而在并發(fā)環(huán)境中安全地使用。這可能意味著用一個 synchronized 塊包圍每一個方法調(diào)用,或者創(chuàng)建一個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。也可能意味著用 synchronized 塊包圍某些操作序列。為了最大程度地利用線程兼容類,如果所有調(diào)用都使用同一個塊,那么就不應(yīng)該要求調(diào)用者對該塊同步。這樣做會使線程兼容的對象作為變量實例包含在其他線程安全的對象中,從而可以利用其所有者對象的同步。
許多常見的類是線程兼容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。
線程對立
線程對立類是那些不管是否調(diào)用了外部同步都不能在并發(fā)使用時安全地呈現(xiàn)的類。線程對立很少見,當(dāng)類修改靜態(tài)數(shù)據(jù),而靜態(tài)數(shù)據(jù)會影響在其他線程中執(zhí)行的其他類的行為,這時通常會出現(xiàn)線程對立。線程對立類的一個例子是調(diào)用 System.setOut() 的類。
其他線程安全記錄考慮
線程安全類(以及線程安全性程度更低的的類) 可以允許或者不允許調(diào)用者鎖定對象以進(jìn)行獨占性訪問。 Hashtable 類對所有的同步使用對象的內(nèi)部監(jiān)視器,但是 ConcurrentHashMap 類不是這樣,事實上沒有辦法鎖定一個 ConcurrentHashMap 對象以進(jìn)行獨占性訪問。除了記錄線程安全程序,還應(yīng)該記錄是否某些鎖 -- 如對象的內(nèi)部鎖 -- 對類的行為有特殊的意義。
通過將類記錄為線程安全的(假設(shè)它確實 是線程安全的),您就提供了兩種有價值的服務(wù):您告知類的維護(hù)者不要進(jìn)行會影響其線程安全性的修改或者擴(kuò)展,您還告知類的用戶使用它時可以不使用外部同步。通過將類記錄為線程兼容或者有條件線程安全的,您就告知了用戶這個類可以通過正確使用同步而安全地在多線程中使用。通過將類記錄為線程對立的,您就告知用戶即使使用了外部同步,他們也不能在多線程中安全地使用這個類。不管是哪種情況,您都在潛在的嚴(yán)重問題出現(xiàn) 之前防止了它們,而要查找和修復(fù)這些問題是很昂貴的。
結(jié)束語
一個類的線程安全行為是其規(guī)格說明中的固有部分,應(yīng)該成為其文檔的一部分。因為(還)沒有描述類的線程安全行為的聲明式方式,所以必須用文字描述。雖然 Bloch 的描述類的線程安全程度的五層系統(tǒng)沒有涵蓋所有可能的情況,但是它是一個很好的起點。如果每一個類都將這種線程行為的程度加入到其 Javadoc 中,那么可以肯定的是我們大家都會受益。
我們知道,在操作系統(tǒng)級別上軟件的運行一般都是以進(jìn)程為單位,而在每個進(jìn)程的運行過程中允許同時并發(fā)執(zhí)行多個不同線程,這就使得一個程序能同時執(zhí)行不同的操作。使用多線程的目的是為了最大限度地利用計算機(jī)CPU資源。JAVA程序字節(jié)碼最終是在JVM虛擬機(jī)下運行的,同一虛擬機(jī)進(jìn)程中的不同操作都是通過多線程來運行的。在JAVA虛擬機(jī)中,線程常用有單線程和多線程,單線程指程序執(zhí)行過程只是一個有效操作的序列,不同操作都有著明確的先后順序;而多線程允許同時進(jìn)行著不同的操作,這些不同的操作同時并發(fā)進(jìn)行著,并由CPU時鐘頻率根據(jù)不同的調(diào)度方式對他們進(jìn)行執(zhí)行調(diào)度。
在JAVA語言中提供了豐富的多線程操縱接口,提供了各類不同的線程實現(xiàn)方法供我們選擇,功能非常強(qiáng)大。在手機(jī)軟件設(shè)計中,由于同樣需要執(zhí)行網(wǎng)絡(luò)連接(基于HTTP的高級Internet協(xié)議通訊)、UI調(diào)度等待、UI顯示幻化、游戲控制等操作需要通過后臺的上海保潔數(shù)據(jù)運算或UI不斷更新等操作。因此在J2ME中,KVM虛擬機(jī)也提供了功能強(qiáng)大的多線程API,使我們同樣能在J2ME中實現(xiàn)線程的并發(fā)運算。
在J2ME中,主要有以下三種方法實現(xiàn)多線程。
一、繼承Thread類(java.lang.Thread)
通過編寫線程類繼承Thread類并重寫Thread類中的run()方法實現(xiàn)線程,當(dāng)線程對象被運行時候?qū)詣訄?zhí)行run方法中的實體內(nèi)容,從而開辟一個單獨的線程并運行起來。
如:public class ThreadSimple extends Thread{
public ThreadSimple()
{
//constructor
}
public void run()
{
//run code entity
}
}
線程實例使用,直接創(chuàng)建對象并調(diào)用start()方法即可運行線程。
new ThreadSimple()。start();當(dāng)執(zhí)行start方法時候,將會自動運行run方法,但是執(zhí)行start方法時候只做了一件事,就是將線程轉(zhuǎn)化為可執(zhí)行狀態(tài),然后等待操作系統(tǒng)進(jìn)行調(diào)度并運行,因此無法保證線程能立即啟動。在JAVA中,Thread類實現(xiàn)了Runnable接口,因此run方法是通過實現(xiàn)接口Runnable中的抽象方法。
二、直接實現(xiàn)Runnable多線程接口(java.lang.Runnable)
線程接口Runnable中只有一個抽象方法run,通過實現(xiàn)Runnable接口中的方法的類即可創(chuàng)建出有多線程特征的對象,但該對象并無法使其啟動線程,需要作為參數(shù)并借助Thread的構(gòu)造方法構(gòu)造創(chuàng)建對象并調(diào)用start方法對線程進(jìn)行啟動。
如:public class RunnablSimple implements Runnable{
public RunnableSimple()
{
//constructor
}
public void run(){
//run code entity
}
}
實現(xiàn)類型的對象使用:
RunnableSimple rs = new RunnableSimple();
new Thread(rs).start();
由此可見,以上兩種方法都是通過Thread的start來啟動線程的,實際上所有的線程操作都是封裝在Thread這個類中,由Thread對象調(diào)用各種接口來控制線程。
J2ME中線程中主要方法:void setPriority(int newPriority),設(shè)置線程優(yōu)先級,在操作系統(tǒng)中線程的調(diào)度是不確定性的,可以通過該方法設(shè)置相應(yīng)線程的優(yōu)先級別。
static void sleep(long millis) ,線程中靜態(tài)方法,用于讓線程進(jìn)入休眠狀態(tài),執(zhí)行該方法將會讓線程在指定時間millis毫秒內(nèi)休眠。
void start(),使現(xiàn)在進(jìn)入可執(zhí)行狀態(tài)。
void run() ,線程執(zhí)行主體。
void join(),等待該線程終止。
boolean isAlive(),用于判斷線程是否出于Alive狀態(tài)。
static void yield() ,盡量讓其他線程先執(zhí)行。
三、使用任務(wù)組合實現(xiàn)多線程
在J2ME中,同樣具有JAVA中的任務(wù)處理組合類,他們分別為Timer和TimerTask,可以使用他們實現(xiàn)多線程,簡單說就是定時實現(xiàn)任務(wù)。
Timer是JAVA中的一個定時器,可以實現(xiàn)在某一時間做某件事或者在某一時間段做某些事,分別通過方法schedule(TimerTask tt,long millis)和schedule(TimerTask tt,long start,long off)。
TimerTask是一個任務(wù)類,通過繼承該類并覆蓋方法run即可創(chuàng)建一個任務(wù)。
如:public class TimerTaskS extends TimerTask{
public TimerTaskS(){
//constructor
}
public void run(){
//run code entity
}
}
任務(wù)調(diào)用:
Timer timer = new Timer();
//3秒鐘后執(zhí)行任務(wù)
timer.schedule(new TimerTaskS(),3000);
//3秒鐘后執(zhí)行任務(wù)并且之后每5秒鐘執(zhí)行一次
timer.schedule(new TimerTaskS(),3000,5000);
有此可見在使用計時任務(wù)可以達(dá)到實現(xiàn)線程的效果,分別執(zhí)行不同的并發(fā)操作,通過Timer類對象來操作TimerTask對象,通過schedule方法來計時執(zhí)行任務(wù),在結(jié)束任務(wù)的時候,通常使用cancel()來實現(xiàn)。
通常情況下,在J2ME軟件中我們通過手機(jī)按鍵來觸發(fā)一系列相應(yīng)的操作,在程序響應(yīng)處理過程中較多會涉及網(wǎng)絡(luò)操作、數(shù)據(jù)存儲等相對消耗時間和資源的操作,而這些操作往往需要一定的時間才能完成,因此在處理按鍵響應(yīng)過程中通常我們需要建立干洗設(shè)備線程處理,避免程序出現(xiàn)死機(jī)現(xiàn)象。
public void commandAction(Command c, Displayable s) {
if(c==do1Com){
//創(chuàng)建實現(xiàn)接口線程
new Thread(new RunnableSimple()).start();
}
else if(c==do2Com){
//創(chuàng)建繼承Thread線程
new ThreadSimple().start();
}
else{
//創(chuàng)建任務(wù)線程
new Timer().schedule(new TimerTaskS(),3000,20);
}
}
描述:
計數(shù)代理模式在客戶對象調(diào)用服務(wù)提供者對象上方法的前后執(zhí)行諸如日志(logging)和計數(shù)(counting)一系列附加功能時很有用。計數(shù)代理模式建議把這些附加功能封裝在一個單獨的對象,這個對象就是指計數(shù)代理對象,而不是把這些附加的功能實現(xiàn)放到服務(wù)提供者的內(nèi)部。良好的對象設(shè)計的一個特征就是對象要專注于提供特定的功能。換句話說,理想的對象不應(yīng)該做各種不相干高周波的事情。把諸如日志(logging)和計數(shù)(counting)等類似的功能封裝為一個單獨的對象,而讓服務(wù)提供者對象僅提供它自己的特定功能。也就是說,只允許服務(wù)提供者對象執(zhí)行定義良好、特定的任務(wù)。
計數(shù)代理被設(shè)計成可以被客戶訪問的與服務(wù)提供者具有相同接口的對象。客戶對象不是直接訪問服務(wù)提供者,而是調(diào)用計數(shù)代理對象上的方法,計數(shù)代理執(zhí)行必要的紀(jì)錄日志(logging)和計數(shù)(counting)功能后,再把方法調(diào)用傳遞給服務(wù)提供著對象。如圖1
Figure1: Generic Class Association When the Counting Proxy Pattern Is Applied
下面的例子說明了如何在應(yīng)用程序中利用計數(shù)代理。
例子:
讓我們設(shè)計一個Order類,類層次如圖2,OrderIF接口聲明了getAllOrders讀取數(shù)據(jù)庫中所有訂單的簡單方法。
Figure2: Order Class Hierarchy
public interface OrderIF {
public Vector getAllOrders();
}
作為getAllOrders方法實現(xiàn)的一部分,Order類實用了FileUtil工具類從order.txt文件中讀取訂單項。
public class Order implements OrderIF {
public Vector getAllOrders() {
FileUtil fileUtil = new FileUtil();
Vector v = fileUtil.fileToVector("orders.txt");
return v;
}
}
讓我們假定在調(diào)用getAllOrders()時,需要把取數(shù)據(jù)文件所花費的時間和記錄條數(shù)要吸親自出吸塑機(jī)記錄的log日志文件中。
這個附加的功能可以設(shè)計一個單獨的OrderProxy類來實現(xiàn),它與真實對象Order一樣實現(xiàn)OrderIF接口。這樣保證了OrderProxy對象提供給客戶與真實對象Order一樣的接口。如圖3
Figure3: Order Class Hierarchy with the Counting Proxy
public class OrderProxy implements OrderIF {
private int counter = 0;
public Vector getAllOrders() {
Order order = new Order();
counter++;
long t1 = System.currentTimeMillis ();
Vector v = order.getAllOrders();
long t2 = System.currentTimeMillis();
long timeDiff = t2 ? t1;
String msg = "Iteration=" + counter + "::Time=" + timeDiff + "ms";
//log the message
FileUtil fileUtil = new FileUtil();
fileUtil.writeToFile("log.txt”,msg, true, true);
return v;
}
}
客戶對象MainApp就想調(diào)用真實對象Order一樣調(diào)用OrderProxy對象上的getAllOrders()方法,OrderProxy對象傳遞這個調(diào)用給真實對象Order,計算讀取所有訂單所花費的時間并使用FileUtil幫助類將其紀(jì)錄的log日志文件中。在這個過程中,OrderProxy扮演者計數(shù)代理的角色。
public class MainApp {
public static void main(String[] args) {
OrderIF order = new OrderProxy();
Vector v = order.getAllOrders();
v = order.getAllOrders();
v = order.getAllOrders();
v = order.getAllOrders();
}
}
在 Java 程序中使用多線程要比在 C 或 C++ 中容易得多,這是因為 Java 編程語言提供了語言級的支持。本文通過簡單的編程示例來說明 Java 程序中的多線程是多么直觀。讀完本文以后,用戶應(yīng)該能夠編寫簡單的多線程程序。
為什么會排隊等待?
下面的這個簡單的 Java 程序完成四項不相關(guān)的任務(wù)。這樣的程序有單個控制線程,控制在這四個任務(wù)之間線性地移動。此外,因為所需的資源 ? 打印機(jī)、磁盤、數(shù)據(jù)庫和顯示屏 -- 由于硬件和軟件的限制都有內(nèi)在的潛伏時間,所以每項任務(wù)都包含明顯的等待時間。因此,程序在訪問數(shù)據(jù)庫之前必須等待打印機(jī)完成打印文件的任務(wù),等等。如果您正在等待程序的完成,則這是對計算資源和您的時間的一種拙劣使用。改進(jìn)此程序的一種方法是使它成為多線程的。
四項不相關(guān)的任務(wù)
class myclass {
static public void main(String args[]) {
print_a_file();
manipulate_another_file();
access_database();
draw_picture_on_screen();
}
}
在本例中,每項任務(wù)在開始之前必須等待前一項任務(wù)完成,即使所涉及的任務(wù)毫不相關(guān)也是這樣。但是,在現(xiàn)實生活中,我們經(jīng)常使用多線程模型。我們在處理某些任務(wù)的同時也可以讓孩子、配偶和父母完成別的任務(wù)。例如,我在寫信的同時可能打發(fā)我的兒子去郵局買郵票。用軟件術(shù)語來說,這稱為多個控制(或執(zhí)行)線程。
可以用兩種不同的方法來獲得多個控制線程:
多個進(jìn)程
在大多數(shù)操作系統(tǒng)中都可以創(chuàng)建多個進(jìn)程。當(dāng)一個程序啟動時,它可以為即將開始的每項任務(wù)創(chuàng)建一個進(jìn)程,并允許它們同時運行。當(dāng)一個程序因等待網(wǎng)絡(luò)訪問或用戶輸入而被阻塞時,另一個程序還可以運行,這樣就增加了干洗設(shè)備資源利用率。但是,按照這種方式創(chuàng)建每個進(jìn)程要付出一定的代價:設(shè)置一個進(jìn)程要占用相當(dāng)一部分處理器時間和內(nèi)存資源。而且,大多數(shù)操作系統(tǒng)不允許進(jìn)程訪問其他進(jìn)程的內(nèi)存空間。因此,進(jìn)程間的通信很不方便,并且也不會將它自己提供給容易的編程模型。
線程
線程也稱為輕型進(jìn)程 (LWP)。因為線程只能在單個進(jìn)程的作用域內(nèi)活動,所以創(chuàng)建線程比創(chuàng)建進(jìn)程要廉價得多。這樣,因為線程允許協(xié)作和數(shù)據(jù)交換,并且在計算資源方面非常廉價,所以線程比進(jìn)程更可取。線程需要操作系統(tǒng)的支持,因此不是所有的機(jī)器都提供線程。Java 編程語言,作為相當(dāng)新的一種語言,已將線程支持與語言本身合為一體,這樣就對線程提供了強(qiáng)健的支持。
使用 Java 編程語言實現(xiàn)線程
Java編程語言使多線程如此簡單有效,以致于某些程序員說它實際上是自然的。盡管在 Java 中使用線程比在其他語言中要容易得多,仍然有一些概念需要掌握。要記住的一件重要的事情是 main() 函數(shù)也是一個線程,并可用來做有用的工作。程序員只有在需要多個線程時才需要創(chuàng)建新的線程。
Thread 類
Thread 類是一個具體的類,即不是抽象類,該類封裝了線程的行為。要創(chuàng)建一個線程,程序員必須創(chuàng)建一個從 Thread 類導(dǎo)出的新類。程序員必須覆蓋 Thread 的 run() 函數(shù)來完成有用的工作。用戶并不直接調(diào)用此函數(shù);而是必須調(diào)用 Thread 的 start() 函數(shù),該函數(shù)再調(diào)用 run()。下面的代碼說明了它的用法:
創(chuàng)建兩個新線程
import java.util.*;
class TimePrinter extends Thread {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + ":" + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);
}
}
}
static public void main(String args[]) {
TimePrinter tp1 = new TimePrinter(1000, "Fast Guy");
tp1.start();
TimePrinter tp2 = new TimePrinter(3000, "Slow Guy");
tp2.start();
}
}
在本例中,我們可以看到一個簡單的程序,它按兩個不同的時間間隔(1 秒和 3 秒)在屏幕上顯示當(dāng)前時間。這是通過創(chuàng)建兩個新線程來完成的,包括 main() 共三個線程。但是,因為有時要作為線程運行的類可能已經(jīng)是某個類層次的一部分,所以就不能再按這種機(jī)制創(chuàng)建干洗機(jī)線程。雖然在同一個類中可以實現(xiàn)任意數(shù)量的接口,但 Java 編程語言只允許一個類有一個父類。同時,某些程序員避免從 Thread 類導(dǎo)出,因為它強(qiáng)加了類層次。對于這種情況,就要 runnable 接口。
Runnable 接口
此接口只有一個函數(shù),run(),此函數(shù)必須由實現(xiàn)了此接口的類實現(xiàn)。但是,就運行這個類而論,其語義與前一個示例稍有不同。我們可以用 runnable 接口改寫前一個示例。(不同的部分用黑體表示。)
創(chuàng)建兩個新線程而不強(qiáng)加類層次
import java.util.*;
class TimePrinter implements Runnable {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + ":" + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);
}
}
}
static public void main(String args[]) {
Thread t1 = new Thread(new TimePrinter(1000, "Fast Guy"));
t1.start();
Thread t2 = new Thread(new TimePrinter(3000, "Slow Guy"));
t2.start();
}
}
請注意,當(dāng)使用 runnable 接口時,您不能直接創(chuàng)建所需類的對象并運行它;必須從 Thread 類的一個實例內(nèi)部運行它。許多程序員更喜歡 runnable 接口,因為從 Thread 類繼承會強(qiáng)加類層次。
synchronized 關(guān)鍵字
到目前為止,我們看到的示例都只是以非常簡單的方式來利用線程。只有最小的數(shù)據(jù)流,而且不會出現(xiàn)兩個線程訪問同一個對象的情況。但是,在大多數(shù)有用的程序中,線程之間通常有信息流。試考慮一個金融應(yīng)用程序,它有一個 Account 對象,如下例中所示:
一個銀行中的多項活動
public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public void deposit(float amt) {
amount += amt;
}
public void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
在此代碼樣例中潛伏著一個錯誤。如果此類用于單線程應(yīng)用程序,不會有任何問題。但是,在多線程應(yīng)用程序的情況中,不同的線程就有可能同時訪問同一個 Account 對象,比如說一個聯(lián)合帳戶的所有者在不同的 ATM 上同時進(jìn)行訪問。在這種情況下,存入和支出就可能以這樣的方式發(fā)生:一個事務(wù)被另一個事務(wù)覆蓋。這種情況將是災(zāi)難性的。但是,Java 編程語言提供了一種簡單的機(jī)制來防止發(fā)生這種覆蓋。每個對象在運行時都有一個關(guān)聯(lián)的鎖。這個鎖可通過為方法添加關(guān)鍵字 synchronized 來獲得。這樣,修訂過的 Account 對象(如下所示)將不會遭受像數(shù)據(jù)損壞這樣的錯誤:
對一個銀行中的多項活動進(jìn)行同步處理
public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public synchronized void deposit(float amt) {
amount += amt;
}
public synchronized void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
deposit() 和 withdraw() 函數(shù)都需要這個鎖來進(jìn)行操作,所以當(dāng)一個函數(shù)運行時,另一個函數(shù)就被阻塞。請注意, checkBalance() 未作更改,它嚴(yán)格是一個讀函數(shù)。因為 checkBalance() 未作同步處理,所以任何其他方法都不會阻塞它,它也不會阻塞任何其他方法,不管那些方法是否進(jìn)行了同步處理。
Java 編程語言中的高級多線程支持
線程組
線程是被個別創(chuàng)建的,但可以將它們歸類到線程組中,以便于調(diào)試和監(jiān)視。只能在創(chuàng)建線程的同時將它與一個線程組相關(guān)聯(lián)。在使用大量線程的程序中,使用線程組組織線程可能很有幫助。可以將它們看作是計算機(jī)上的目錄和文件結(jié)構(gòu)。
線程間發(fā)信
當(dāng)線程在繼續(xù)執(zhí)行前需要等待一個條件時,僅有 synchronized 關(guān)鍵字是不夠的。雖然 synchronized 關(guān)鍵字阻止并發(fā)更新一個對象,但它沒有實現(xiàn)線程間發(fā)信。Object 類為此提供了三個函數(shù):wait()、notify() 和 notifyAll()。以全球氣候預(yù)測程序為例。這些程序通過將地球分為許多單元,在每個循環(huán)中,每個單元的計算都是隔離進(jìn)行的,直到這些值趨于穩(wěn)定,然后相鄰單元之間就會交換一些數(shù)據(jù)。所以,從本質(zhì)上講,在每個循環(huán)中各個線程都必須等待所有線程完成各自的任務(wù)以后才能進(jìn)入下一個循環(huán)。這個模型稱為 屏蔽同步,下例說明了這個模型:
屏蔽同步
public class BSync {
int totalThreads;
int currentThreads;
public BSync(int x) {
totalThreads = x;
currentThreads = 0;
}
public synchronized void waitForAll() {
currentThreads++;
if(currentThreads < totalThreads) {
try {
wait();
} catch (Exception e) {}
}
else {
currentThreads = 0;
notifyAll();
}
}
}
當(dāng)對一個線程調(diào)用 wait() 時,該線程就被有效阻塞,只到另一個線程對同一個對象調(diào)用 notify() 或 notifyAll() 為止。因此,在前一個示例中,不同的線程在完成它們的工作以后將調(diào)用 waitForAll() 函數(shù),最后一個線程將觸發(fā) notifyAll() 函數(shù),該函數(shù)將釋放所有的線程。第三個函數(shù) notify() 只通知一個正在等待的線程,當(dāng)對每次只能由一個線程使用的資源進(jìn)行訪問限制時,這個函數(shù)很有用。但是,不可能預(yù)知哪個線程會獲得這個通知,因為這取決于 Java 虛擬機(jī) (JVM) 調(diào)度算法。
將 CPU 讓給另一個線程
當(dāng)線程放棄某個稀有的資源(如數(shù)據(jù)庫連接或網(wǎng)絡(luò)端口)時,它可能調(diào)用 yield() 函數(shù)臨時降低自己的優(yōu)先級,以便某個其他線程能夠運行。
守護(hù)線程
有兩類線程:用戶線程和守護(hù)線程。用戶線程是那些完成有用工作的線程。 守護(hù)線程是那些僅提供輔助功能的線程。Thread 類提供了 setDaemon() 函數(shù)。Java 程序?qū)⑦\行到所有用戶線程終止,然后它將破壞所有的守護(hù)線程。在 Java 虛擬機(jī) (JVM) 中,即使在 main 結(jié)束以后,如果另一個用戶線程仍在運行,則程序仍然可以繼續(xù)運行。
避免不提倡使用的方法
不提倡使用的方法是為支持向后兼容性而保留的那些方法,它們在以后的版本中可能出現(xiàn),也可能不出現(xiàn)。Java 多線程支持在版本 1.1 和版本 1.2 中做了重大修訂,stop()、suspend() 和 resume() 函數(shù)已不提倡使用。這些函數(shù)在 JVM 中可能引入微妙的錯誤。雖然函數(shù)名可能聽起來很誘人,但請抵制誘惑不要使用它們。
調(diào)試線程化的程序
在線程化的程序中,可能發(fā)生的某些常見而討厭的情況是死鎖、活鎖、內(nèi)存損壞和資源耗盡。
死鎖
死鎖可能是多線程程序最常見的問題。當(dāng)一個線程需要一個資源而另一個線程持有該資源的鎖時,就會發(fā)生死鎖。這種情況通常很難檢測。但是,解決方案卻相當(dāng)好:在所有的線程中按相同的次序獲取所有資源鎖。例如,如果有四個資源 ?A、B、C 和 D ? 并且一個線程可能要獲取四個資源中任何一個資源的鎖,則請確保在獲取對 B 的鎖之前首先獲取對 A 的鎖,依此類推。如果“線程 1”希望獲取對 B 和 C 的鎖,而“線程 2”獲取了 A、C 和 D 的鎖,則這一技術(shù)可能導(dǎo)致阻塞,但它永遠(yuǎn)不會在這四個鎖上造成死鎖。
活鎖
當(dāng)一個線程忙于接受新任務(wù)以致它永遠(yuǎn)沒有機(jī)會完成任何任務(wù)時,就會發(fā)生活鎖。這個線程最終將超出緩沖區(qū)并導(dǎo)致程序崩潰。試想一個秘書需要錄入一封信,但她一直在忙于接電話,所以這封信永遠(yuǎn)不會被錄入。
內(nèi)存損壞
如果明智地使用 synchronized 關(guān)鍵字,則完全可以避免內(nèi)存錯誤這種氣死人的問題。
資源耗盡
某些系統(tǒng)資源是有限的,如文件描述符。多線程程序可能耗盡資源,因為每個線程都可能希望有一個這樣的資源。如果線程數(shù)相當(dāng)大,或者某個資源的侯選線程數(shù)遠(yuǎn)遠(yuǎn)超過了可用的資源數(shù),則最好使用 資源池。一個最好的示例是數(shù)據(jù)庫連接池。只要線程需要使用一個數(shù)據(jù)庫連接,它就從池中取出一個,使用以后再將它返回池中。資源池也稱為 資源庫。
調(diào)試大量的線程
有時一個程序因為有大量的線程在運行而極難調(diào)試。在這種情況下,下面的這個類可能會派上用場:
public class Probe extends Thread {
public Probe() {}
public void run() {
while(true) {
Thread[] x = new Thread[100];
Thread.enumerate(x);
for(int i=0; i<100; i++) {
Thread t = x[i];
if(t == null)
break;
else
System.out.println(t.getName() + "\t" + t.getPriority()
+ "\t" + t.isAlive() + "\t" + t.isDaemon());
}
}
}
}
限制線程優(yōu)先級和調(diào)度
Java 線程模型涉及可以動態(tài)更改的線程優(yōu)先級。本質(zhì)上,線程的優(yōu)先級是從 1 到 10 之間的一個數(shù)字,數(shù)字越大表明任務(wù)越緊急。JVM 標(biāo)準(zhǔn)首先調(diào)用優(yōu)先級較高的線程,然后才調(diào)用優(yōu)先級較低的線程。但是,該標(biāo)準(zhǔn)對具有相同優(yōu)先級的線程的處理是隨機(jī)的。如何處理這些線程取決于基層的操作系統(tǒng)策略。在某些情況下,優(yōu)先級相同的線程分時運行;在另一些情況下,線程將一直運行到結(jié)束。請記住,Java 支持 10 個優(yōu)先級,基層操作系統(tǒng)支持的優(yōu)先級可能要少得多,這樣會造成一些混亂。因此,只能將優(yōu)先級作為一種很粗略的工具使用。最后的控制可以通過明智地使用 yield() 函數(shù)來完成。通常情況下,請不要依靠線程優(yōu)先級來控制線程的狀態(tài)。
小結(jié)
本文說明了在 Java 程序中如何使用線程。像是否應(yīng)該使用線程這樣的更重要的問題在很大程序上取決于手頭的應(yīng)用程序。決定是否在應(yīng)用程序中使用多線程的一種方法是,估計可以并行運行的代碼量。并記住以下幾點:
使用多線程不會增加 CPU 的能力。但是如果使用 JVM 的本地線程實現(xiàn),則不同的線程可以在不同的處理器上同時運行(在多 CPU 的機(jī)器中),從而使多 CPU 機(jī)器得到充分利用。
如果應(yīng)用程序是計算密集型的,并受 CPU 功能的制約,則只有多 CPU 機(jī)器能夠從更多的線程中受益。
當(dāng)應(yīng)用程序必須等待緩慢的資源(如網(wǎng)絡(luò)連接或數(shù)據(jù)庫連接)時,或者當(dāng)應(yīng)用程序是非交互式的時,多線程通常是有利的。
基于 Internet 的軟件有必要是多線程的;否則,用戶將感覺應(yīng)用程序反映遲鈍。例如,當(dāng)開發(fā)要支持大量客戶機(jī)的服務(wù)器時,多線程可以使編程較為容易。在這種情況下,每個線程可以為不同的客戶或客戶組服務(wù),從而縮短了響應(yīng)時間。
某些程序員可能在 C 和其他語言中使用過線程,在那些語言中對線程沒有語言支持。這些程序員可能通常都被搞得對線程失去了信心。
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class RSACoder {
/**
* 得到公鑰
* @param key 密鑰字符串(經(jīng)過base64編碼)
* @throws Exception
*/
public static PublicKey getPublicKey(String key) throws Exception {
byte[] keyBytes;
keyBytes = (new BASE64Decoder()).decodeBuffer(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
/**
* 得到私鑰
* @param key 密鑰字符串(經(jīng)過base64編碼)
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key) throws Exception {
byte[] keyBytes;
keyBytes = (new BASE64Decoder()).decodeBuffer(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
/**
* 得到密鑰字符串(經(jīng)過base64編碼)
* @return
*/
public static String getKeyString(Key key) throws Exception {
byte[] keyBytes = key.getEncoded();
String s = (new BASE64Encoder()).encode(keyBytes);
return s;
}
public static void main(String[] args) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
//密鑰位數(shù)
keyPairGen.initialize(1024);
//密鑰對
KeyPair keyPair = keyPairGen.generateKeyPair();
// 公鑰
PublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 私鑰
PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = getKeyString(publicKey);
System.out.println("public:\n" + publicKeyString);
String privateKeyString = getKeyString(privateKey);
System.out.println("private:\n" + privateKeyString);
//加解密類
Cipher cipher = Cipher.getInstance("RSA");//Cipher.getInstance("RSA/ECB/PKCS1Padding");
//明文
byte[] plainText = "我們都很好!郵件:@sina.com".getBytes();
//加密
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] enBytes = cipher.doFinal(plainText);
//通過密鑰字符串得到密鑰
publicKey = getPublicKey(publicKeyString);
privateKey = getPrivateKey(privateKeyString);
//解密
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[]deBytes = cipher.doFinal(enBytes);
publicKeyString = getKeyString(publicKey);
System.out.println("public:\n" +publicKeyString);
privateKeyString = getKeyString(privateKey);
System.out.println("private:\n" + privateKeyString);
String s = new String(deBytes);
System.out.println(s);
}
}
最近在開發(fā)Java的程序。本來我是一直很喜歡Java的內(nèi)存管理的,不需要擔(dān)心分配內(nèi)存,只管分配,垃圾收集器自己會給你回收內(nèi)存的。現(xiàn)在開發(fā)的程序數(shù)據(jù)量很大,為了速度快,我準(zhǔn)備把所有的信息加載進(jìn)內(nèi)存,這樣可以保證快速響應(yīng)。我還在反復(fù)算內(nèi)存,想想自己的數(shù)據(jù)量,現(xiàn)在剛開始的時候應(yīng)該夠了(我的機(jī)器是4G內(nèi)存,雖然Windows就認(rèn)3.5G,但是比起我現(xiàn)在的數(shù)據(jù)量應(yīng)該沒問題)。
沒想到第一個實驗的程序,跑了幾個小時,就遇到了Out of Memory Exception了。看看自己的虛擬機(jī)設(shè)置,我設(shè)置的是-Xms512M -Xmx1024M。想都沒想,直接改成-Xms512M -Xmx2048M,結(jié)果直接就Could not reserve enough space for object heap。程序都起不來了。這才發(fā)現(xiàn)原來最大內(nèi)存還有限制。上網(wǎng)搜了一下干洗機(jī),發(fā)現(xiàn)很多討論這個問題的文章。最終在BEA的DEV2DEV論壇發(fā)現(xiàn)了最有用的一篇http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID= 35704&start=0&tstart=0
這里的版主YuLimin 做了測試,得出結(jié)論:
公司 JVM版本 最大內(nèi)存(兆)client 最大內(nèi)存(兆)server
SUN 1.5.x 1492 1520
SUN 1.5.5(Linux) 2634 2660
SUN 1.4.2 1564 1564
SUN 1.4.2(Linux) 1900 1260
IBM 1.4.2(Linux) 2047 N/A
BEA JRockit 1.5 (U3) 1909 1902
我現(xiàn)在用的是JDK1.6. 0_05,測試了一下。在Client狀態(tài)下最大是,我的JDK不認(rèn)-Server參數(shù),測試不了Server狀態(tài)。估計差不多。
SUN 1.6.0 1442 N/a
看樣子用Java想用大內(nèi)存也是不可能的了。而且一般的說法是內(nèi)存太大了,垃圾收集的時間就會長。這也可以理解,一般是內(nèi)存不夠用了才收集的,掃描2G內(nèi)存比1G當(dāng)然要慢多了,而且內(nèi)存對象多了,估計關(guān)系是指數(shù)上升的。
下面附上YuLimin的測試方法和測試記錄。
測試方法:在命令行下用 java -XmxXXXXM -version 命令來進(jìn)行測試,然后逐漸的增大XXXX的值,如果執(zhí)行正常就表示指定的內(nèi)存大小可用,否則會打印錯誤信息。
測試記錄:
我在Windows 2000 ADS上面測試內(nèi)存使用的結(jié)果如下
SUN的1.2.2、1.3.1、1.4.2、1.5.0、IBM1.4.2、BEA JRockit 1.4.2
F:\JDK\1.2.2\bin>java -Xmx700000255M -version
java version “1.2.2″
Classic VM (build JDK-1.2.2_017, native threads, symcjit)
F:\JDK\1.2.2\bin>java -Xmx700000256M -version
Bad max heap size: -Xmx700000256M
Could not create the Java virtual machine.
=====================================================================
F:\JDK\1.3.1\bin>java -version
java version “1.3.1_18″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_18-b01)
Java HotSpot(TM) Client VM (build 1.3.1_18-b01, mixed mode)
F:\JDK\1.3.1\bin>REM If present, the option to select the VM must be first.
F:\JDK\1.3.1\bin>REM The default VM is -hotspot.
F:\JDK\1.3.1\bin>java -hotspot -Xmx1554M -version
java version “1.3.1_18″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_18-b01)
Java HotSpot(TM) Client VM (build 1.3.1_18-b01, mixed mode)
F:\JDK\1.3.1\bin>java -hotspot -Xmx1555M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
F:\JDK\1.3.1\bin>java -server -Xmx1522M -version
java version “1.3.1_18″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_18-b01)
Java HotSpot(TM) Server VM (build 1.3.1_18-b01, mixed mode)
F:\JDK\1.3.1\bin>java -server -Xmx1523M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
F:\JDK\1.3.1\bin>java -classic -Xmx2047M -version
java version “1.3.1_18″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_18-b01)
Classic VM (build 1.3.1_18-b01, native threads, nojit)
F:\JDK\1.3.1\bin>java -classic -Xmx2048M -version
Bad max heap size: -Xmx2048M
Could not create the Java virtual machine.
=====================================================================
F:\JDK\1.4.2\bin>java -version
java version “1.4.2_12″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_12-b03)
Java HotSpot(TM) Client VM (build 1.4.2_12-b03, mixed mode)
F:\JDK\1.4.2\bin>REM The default VM is client.
F:\JDK\1.4.2\bin>java -client -Xmx1308M -version
java version “1.4.2_12″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_12-b03)
Java HotSpot(TM) Client VM (build 1.4.2_12-b03, mixed mode)
F:\JDK\1.4.2\bin>java -client -Xmx1309M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
F:\JDK\1.4.2\bin>java -server -Xmx1308M -version
java version “1.4.2_12″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_12-b03)
Java HotSpot(TM) Server VM (build 1.4.2_12-b03, mixed mode)
F:\JDK\1.4.2\bin>java -server -Xmx1309M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
F:\JDK\1.4.2\bin>REM -hotspot is a synonym for the “client” VM [deprecated]
F:\JDK\1.4.2\bin>java -hotspot -Xmx1308M -version
java version “1.4.2_12″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_12-b03)
Java HotSpot(TM) Client VM (build 1.4.2_12-b03, mixed mode)
F:\JDK\1.4.2\bin>java -hotspot -Xmx1309M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
F:\JDK\1.5.0\bin>java -version
java version “1.5.0_07″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)
Java HotSpot(TM) Client VM (build 1.5.0_07-b03, mixed mode, sharing)
F:\JDK\1.5.0\bin>java -client -Xmx1492M -version
java version “1.5.0_07″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)
Java HotSpot(TM) Client VM (build 1.5.0_07-b03, mixed mode)
F:\JDK\1.5.0\bin>java -client -Xmx1493M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
Could not create the Java virtual machine.
F:\JDK\1.5.0\bin>java -server -Xmx1504M -version
java version “1.5.0_07″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)
Java HotSpot(TM) Server VM (build 1.5.0_07-b03, mixed mode)
F:\JDK\1.5.0\bin>java -server -Xmx1505M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
Could not create the Java virtual machine.
F:\JDK\1.5.0\bin>REM -hotspot is a synonym for the “client” VM [deprecated]
F:\JDK\1.5.0\bin>java -hotspot -Xmx1492M -version
java version “1.5.0_07″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-b03)
Java HotSpot(TM) Client VM (build 1.5.0_07-b03, mixed mode)
F:\JDK\1.5.0\bin>java -hotspot -Xmx1493M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
Could not create the Java virtual machine.
=====================================================================
F:\JDK\IBM142\bin>java -version
java version “1.4.2″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2)
Classic VM (build 1.4.2, J2RE 1.4.2 IBM Windows 32 build cn1420-20040626 (JIT enabled: jitc))
F:\JDK\IBM142\bin>REM The default VM is client.
F:\JDK\IBM142\bin>java -Xmx2047M -version
java version “1.4.2″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2)
Classic VM (build 1.4.2, J2RE 1.4.2 IBM Windows 32 build cn1420-20040626 (JIT enabled: jitc))
F:\JDK\IBM142\bin>java -Xmx2048M -version
[ Unable to allocate an initial java heap of 2147483648 bytes. ]
[ **Out of memory, aborting** ]
[ ]
[ *** panic: JVMST016: Cannot allocate memory for initial java heap ]
abnormal program termination
F:\BEA\JRockit\bin>java -version
java version “1.4.2_05″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
BEA WebLogic JRockit(TM) 1.4.2_05 JVM R24.4.0-1 (build ari-38120-20041118-1131-win-ia32, Native Threads, GC strategy: parallel)
F:\BEA\JRockit\bin>REM The default VM is jrockit.
F:\BEA\JRockit\bin>java -Xmx1617M -version
java version “1.4.2_05″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
BEA WebLogic JRockit(TM) 1.4.2_05 JVM R24.4.0-1 (build ari-38120-20041118-1131-win-ia32, Native Threads, GC strategy: parallel)
F:\BEA\JRockit\bin>java -Xmx1618M -version
Unable to acquire some virtual address space - reduced from 1656832 to 1640260 KB!
java version “1.4.2_05″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
BEA WebLogic JRockit(TM) 1.4.2_05 JVM R24.4.0-1 (build ari-38120-20041118-1131-win-ia32, Native Threads, GC strategy: parallel)
F:\BEA\JRockit\bin>REM -jrockit to select the “jrockit” VM
F:\BEA\JRockit\bin>java -jrockit -Xmx1617M -version
java version “1.4.2_05″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
BEA WebLogic JRockit(TM) 1.4.2_05 JVM R24.4.0-1 (build ari-38120-20041118-1131-win-ia32, Native Threads, GC strategy: parallel)
F:\BEA\JRockit\bin>java -jrockit -Xmx1618M -version
Unable to acquire some virtual address space - reduced from 1656832 to 1640260 KB!
java version “1.4.2_05″
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
BEA WebLogic JRockit(TM) 1.4.2_05 JVM R24.4.0-1 (build ari-38120-20041118-1131-win-ia32, Native Threads, GC strategy: parallel)
我的測試記錄:
C:\>java -client -Xmx1441M -version
java version “1.6.0_05″
Java(TM) SE Runtime Environment (build 1.6.0_05-b13)
Java HotSpot(TM) Client VM (build 10.0-b19, mixed mode)
C:\>java -client -Xmx1442M -version
java version “1.6.0_05″
Java(TM) SE Runtime Environment (build 1.6.0_05-b13)
Java HotSpot(TM) Client VM (build 10.0-b19, mixed mode)
C:\>java -client -Xmx1443M -version
Error occurred during initialization of VM
Could not reserve enough space for object heap
Could not create the Java virtual machine.
C:\>java -server -Xmx1443M -version
Error: no `server’ JVM at `C:\Program Files\Java\jre1.6.0_05\bin\server\jvm.dll’
基本數(shù)據(jù)的類型的大小是固定的,這里就不多說了。對于非基本類型的Java對象,其大小就值得商榷。
在Java中,一個空Object對象的大小是8byte,這個大小只是保存堆中一個沒有任何屬性的對象的大小。看下面語句:
Object ob = new Object();
這樣在程序中完成了一個Java對象的生命,但是它所占的空間為:4byte+8byte。4byte是上面部分所說的Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對象的信息。因為所有的Java非基本類型的對象都需要默認(rèn)繼承Object對象,因此不論什么樣的Java對象,其大小都必須是大于8byte。
有了Object對象的大小,我們就可以計算其他對象的大小了。
Class NewObject { int count; boolean flag; Object ob; }
其大小為:空對象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因為Java在對對象內(nèi)存分配時都是以8的整數(shù)倍來分,因此大于17byte的最接近8的整數(shù)倍的是24,因此此對象的大小為24byte。
這里需要注意一下基本類型的包裝類型的大小。因為這種包裝類型已經(jīng)成為對象了,因此需要把他們作為對象來看待。包裝類型的大小至少是12byte(聲明一個空Object至少需要的空間),而且12byte沒有包含任何有效衛(wèi)星電視信息,同時,因為Java對象大小是8的整數(shù)倍,因此一個基本類型包裝類的大小至少是16byte。這個內(nèi)存占用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內(nèi)存占用更是夸張(隨便想下就知道了)。因此,可能的話應(yīng)盡量少使用包裝類。在JDK5.0以后,因為加入了自動類型裝換,因此,Java虛擬機(jī)會在存儲方面進(jìn)行相應(yīng)的優(yōu)化。
引用類型
對象引用類型分為強(qiáng)引用、軟引用、弱引用和虛引用。
強(qiáng)引用:就是我們一般聲明對象是時虛擬機(jī)生成的引用,強(qiáng)引用環(huán)境下,垃圾回收時需要嚴(yán)格判斷當(dāng)前對象是否被強(qiáng)引用,如果被強(qiáng)引用,則不會被垃圾回收
軟引用:軟引用一般被做為緩存來使用。與強(qiáng)引用的區(qū)別是,軟引用在垃圾回收時,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的剩余內(nèi)存來決定是否對軟引用進(jìn)行回收。如果剩余內(nèi)存比較緊張,則虛擬機(jī)會回收軟引用所引用的空間;如果剩余內(nèi)存相對富裕,則不會進(jìn)行回收。換句話說,虛擬機(jī)在發(fā)生OutOfMemory時,肯定是沒有軟引用存在的。
弱引用:弱引用與軟引用類似,都是作為緩存來使用。但與軟引用不同,弱引用在進(jìn)行垃圾回收時,是一定會被回收掉的,因此其生命周期只存在于一個垃圾回收周期內(nèi)。
強(qiáng)引用不用說,我們系統(tǒng)一般在使用時都是用的強(qiáng)引用。而“軟引用”和“弱引用”比較少見。他們一般被作為緩存使用,而且一般是在內(nèi)存大小比較受限的情況下做為緩存。因為如果內(nèi)存足夠大的話,可以直接使用強(qiáng)引用作為緩存即可,同時可控性更高。因而,他們常見的是被使用在桌面應(yīng)用系統(tǒng)的緩存。
所有的Hibernate應(yīng)用中都會訪問Hibernate的5個核心接口。
Configuration接口:配置Hibernate,根啟動Hibernate,創(chuàng)建SessionFactory對象。
SessionFactory接口:初始化Hibernate,充當(dāng)數(shù)據(jù)存儲源的代理,創(chuàng)建Session對象。
Session接口:負(fù)責(zé)保存、更新、刪除、加載和查詢對象。
Transaction:管理事務(wù)。
Query和Criteria接口:執(zhí)行數(shù)據(jù)庫查詢。
1.Configuration接口
Configuration對象用于配置并且啟動Hibernate。Hibernate應(yīng)用通過Configuration實例來指定對象-關(guān)系映射文件的位置或者動態(tài)配置Hibernate的屬性,然后創(chuàng)建SessionFactory實例。
2.SessionFactory接口
一個SessionFactory實例對應(yīng)一個數(shù)據(jù)存儲源,應(yīng)用從SessionFactory中獲得Session實例。SessionFactory有以下特點:
它是線程安全的,這意味著它的同一個實例可以被應(yīng)用的多個線程共享。
它是重量級的,這意味著不能隨意創(chuàng)建或銷毀它的實例。如果應(yīng)用只訪問一個數(shù)據(jù)庫,只需要創(chuàng)建一個SessionFactory實例,在應(yīng)用初始化的時候創(chuàng)建該實例。如果應(yīng)用同時訪問多個數(shù)據(jù)庫,則需要為每個干洗機(jī)創(chuàng)建數(shù)據(jù)庫創(chuàng)建一個單獨的SessionFactory實例。
之所以稱SessionFactory是重量級的,是因為它需要一個很大的緩存,用來存放預(yù)定義的SQL語句以能映射元數(shù)據(jù)等。用戶還可以為SesionFactory配置一個緩存插件,這個緩存插件被稱為Hibernate的第二級緩存。,該緩存用來存放被工作單元讀過的數(shù)據(jù),將來其他工作單元可能會重用這些數(shù)據(jù),因此這個緩存中的數(shù)據(jù)能夠被所有工作單元共享。一個工作單元通常對應(yīng)一個數(shù)據(jù)庫事務(wù)。
3.Session接口
Session接口是Hibernate應(yīng)用使用最廣泛的接口。Session也被稱為持久化管理器,它提供了和持久化相關(guān)的操作,如添加、更新、刪除、加載和查詢對象。
Session有以下特點:
不是線程安全的,因此在設(shè)計軟件架構(gòu)時,應(yīng)該避免多個線程共享同一個Session實例。
Session實例是輕量級的,所謂輕量級,是指它的創(chuàng)建和銷毀不需要消耗太多的資源。這意味著在程序中可以經(jīng)常創(chuàng)建和銷毀Session對象,例如為每個客戶請示分配單獨的Session實例,或者為每個工作單元分配單獨的Session實例。
Session有一個緩存,被稱為Hibernate的第一級緩存,它存放被當(dāng)前工作單元加載的對象。每個Session實例都有自己的緩存,這個Sesion實例的緩存只能被當(dāng)前工作單元訪問。
4.Transaction接口
Transaction接口是Hibernate的數(shù)據(jù)庫事務(wù)接口,它對底層的事務(wù)接口做了封裝,底層事務(wù)接口包括:
JDBC API、JTA(Java Transaction API)、CORBA(Common Object Requet Broker Architecture)API
Hibernate應(yīng)用可通過一致的Transaction接口來聲明事務(wù)邊界,這有助于應(yīng)用在不同的環(huán)境容器中移植。盡管應(yīng)用也可以繞過Transaction接口,直接訪問底層的事務(wù)接口,這種方法不值得推薦,因為它不利于應(yīng)用在不同的環(huán)境移植。
5.Query和Criteria接口
Query和Criteria接口是Hibernate的查詢接口,用于向數(shù)據(jù)庫查詢對象,以及控制執(zhí)行查詢的過程。Query實例包裝了一個HQL查詢語句,HQL查詢語句和SQL查詢語句有些相似,但HQL查詢語句是面向?qū)ο蟮模妙惥浼邦惖膶傩跃洌皇潜砭浼氨淼淖侄尉洹riteria接口完全封裝了基于字符串的查詢語句,比Query接口更加面向?qū)ο螅珻riteria接口擅長執(zhí)行動態(tài)查詢。
Session接口的find()方法也具有數(shù)據(jù)查詢功能,但它只是執(zhí)行一些簡單的HQL查詢語句的快捷方法,它的功能遠(yuǎn)沒有Query接口強(qiáng)大。