2010年7月29日
#
Sun推出的專業認證包括下列三種:
◆JAVA認證考試
對于JAVA程序設計員,Sun推出兩項認證:
Sun Certified JAVA Programmer(SCJP)
Sun Certified JAVA Developer(SCJD)
Java程序員的認證Sun Certified JAVA Programmer(SCJP)課程:SL-275JAVA語言編程,考試號為310-025.
java開發員認證Sun Certified JAVA Deverloper(SCJD),認證考試以Sun指定的javaSL-285為教材,機考部分的考試號為310-027。
SCJP測驗JAVA程序設計概念及能力,內容偏重于JAVA語法及JDK的內容;SCJD則進一步測試用JAVA開發應用程序的能力,考試者必須先完成一個程序的設計方案,再回答與此方案相關的一些問題。
◆Solaris系統管理認證考試
對Solaris/SunOS系統管理員,Sun推出Certified Solaris Administrator(CSA)。CSA分別為兩個等級(PartI和PartII),測試對Solaris系統管理的了解程度。
◆Solaris網絡管理認證考試
為了測試使用者對于Solaris網絡管理能力,Sun推出Certified Network Administrator(CNA)。內容包括基本網絡概念、RoutingandSubnet、Security、Performance、DNS、NIS+等。
SunJava認證是業界唯一經Sun授權Java認證培訓。Sun認證Java開發員考試內容包括完整的Java應用程序開發,涉及數據庫、圖形用戶界面、網絡通信、平臺移植等各方面內容,要求學員已通過Java程序員認證。學習結束后,可參加全球連網考試。考試合格則由Sun公司頒發國際通用的Java開發員證書。
Java語言在1995年發布以來,因其具有簡單性、跨平臺、面向對象等特點,受到程序員們的歡迎,經過這幾年的迅速發展,現在已經成為和C++語言并列的主流開發語言。Java語言的廣泛使用,使得越來越多的人加入到Java語言的學習和應用中來。
在這些人當中,許多人選擇了經過Sun公司授權的Sun教育中心(ASEC)來學習,因為和一些非授權的中心相比,只有ASEC受到Sun組合機床公司的關注和支持,他們可以提供最新的培訓材料和Sun需要的精深技術資源。學員在那里可以學習到最新的Java技術,更順利地通過SunJava的認證考試。
了解SunJava認證課程
Java不僅僅是一種編程語言,同時它也是一個開發和運行的平臺,有自己完整的體系結構。SunJava認證課程從中選擇了最具有代表性的一些方面。
SCJP(Sun Certified Java Programmer)可以說是各種Java認證的基礎,其對應的最主要的學習課程是一門Java的基礎課程,也就是Java Programming Language(SL-275),這也是國內的SCJP培訓的標準課程。而SCJD(Sun Certified Java Developer)則可以看做是高級的Java技術培訓認證,其要求和難度都要高于SCJP,而且,如果你計劃獲得SCJD認證,則要先獲得SCJP認證資格。SCEA(Sun Certified Enterprise Architect for J2EE Technology)認證的難度也不小,其學習課程主要有兩個:00-226O bject-Oriented Analysisand Design以及SL-425 Architectingand Designing J2EE Applications。SCWD(Sun Certified WebComponent Developer for Java2 Platform Enterprise Edition)是Sun新推出的Java認證考試,主要面向使用JavaServlet以及JSP技術開發Web應用程序的相關技術認證。
如何獲取Java認證
首先,你需要有充分的心理準備,因為SunJava認證考試非常嚴謹,需要你具備充足的實踐經驗才可能通過。
數據庫連接池概述:
數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現得尤為突出。對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫連接池正是針對這個問題提出來的。
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而再不是重新建立一個;釋放空閑時間超過最大空閑時間的數據庫連接來避免因為沒有釋放數據庫連接而引起的數據庫連接遺漏。這項技術能明顯提高對數據庫操作的性能。
數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量。連接池的最大數據庫連接干洗設備數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。數據庫連接池的最小連接數和最大連接數的設置要考慮到下列幾個因素:
1) 最小連接數是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費;
2) 最大連接數是連接池能申請的最大連接數,如果數據庫連接請求超過此數,后面的數據庫連接請求將被加入到等待隊列中,這會影響之后的數據庫操作。
3) 如果最小連接數與最大連接數相差太大,那么最先的連接請求將會獲利,之后超過最小連接數量的連接請求等價于建立一個新的數據庫連接。不過,這些大于最小連接數的數據庫連接在使用完不會馬上被釋放,它將被放到連接池中等待重復使用或是空閑超時后被釋放。
目前常用的連接池有:C3P0、DBCP、Proxool
網上的評價是:
C3P0比較耗費資源,效率方面可能要低一點。
DBCP在實踐中存在BUG,在某些種情會產生很多空連接不能釋放,Hibernate3.0已經放棄了對其的支持。
Proxool的負面評價較少,現在比較推薦它,而且它還提供即時監控連接池狀態的功能,便于發現連接泄漏的情況。
配置如下:
1、在spring配置文件中,一般在applicationContext.xml中
<bean id="DataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource" destroy-method="shutdown">
<property name="driver">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="driverUrl">
<value>jdbc:oracle:thin:xxxx/xxxx@192.168.0.XX:1521:server</value>
</property>
<property name="user">
<value>xxxx</value>
</property>
<property name="password">
<value>xxxx</value>
</property>
<property name="alias">
<value>server</value>
</property>
<property name="houseKeepingSleepTime">
<value>30000</value>
</property>
<property name="houseKeepingTestSql">
<value>select 1 from dual</value>
</property>
<property name="testBeforeUse">
<value>true</value>
</property>
<property name="testAfterUse">
<value>true</value>
</property>
<property name="prototypeCount">
<value>5</value>
</property>
<property name="maximumConnectionCount">
<value>400</value>
</property>
<property name="minimumConnectionCount">
<value>10</value>
</property>
<property name="statistics">
<value>1m,15m,1d</value>
</property>
<property name="statisticsLogLevel">
<value>ERROR</value>
</property>
<property name="trace">
<value>true</value>
</property>
<property name="verbose">
<value>false</value>
</property>
<property name="simultaneousBuildThrottle">
<value>1600</value>
</property>
<property name="maximumActiveTime">
<value>600000</value>
</property>
<property name="jmx">
<value>false</value>
</property>
</bean>
然后注入到sessionFactory中
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="DataSource"/>
</bean>
屬性列表說明:
fatal-sql-exception: 它是一個逗號分割的信息片段.當一個SQL異常發生時,他的異常信息將與這個信息片段進行比較.如果在片段中存在,那么這個異常將被認為是個致命錯誤(Fatal SQL Exception ).這種情況下,數據庫連接將要被放棄.無論發生什么,這個異常將會被重擲以提供給消費者.用戶最好自己配置一個不同的異常來拋出.
fatal-sql-exception-wrapper-class:正如上面所說,你最好配置一個不同的異常來重擲.利用這個屬性,用戶可以包裝SQLException,使他變成另外一個異常.這個異常或者繼承QLException或者繼承字RuntimeException.proxool自帶了2個實現:'org.logicalcobwebs.proxool.FatalSQLException'和'org.logicalcobwebs.proxool.FatalRuntimeException'.后者更合適.
house-keeping-sleep-time: house keeper 保留線程處于睡眠狀態的最長時間,house keeper 的職責就是檢查各個連接的狀態,并判斷是否需要銷毀或者創建.
house-keeping-test-sql: 如果發現了空閑的數據庫連接.house keeper 將會用這個語句來測試.這個語句最好非常快的被執行.如果沒有定義,測試過程將會被忽略。
injectable-connection-interface: 允許proxool實現被代理的connection對象的方法.
injectable-statement-interface: 允許proxool實現被代理的Statement 對象方法.
injectable-prepared-statement-interface: 允許proxool實現被代理的PreparedStatement 對象方法.
injectable-callable-statement-interface: 允許proxool實現被代理的CallableStatement 對象方法.
jmx: 如果屬性為true,就會注冊一個消息Bean到jms服務,消息Bean對象名: "Proxool:type=Pool, name=<alias>". 默認值為false.
jmx-agent-id: 一個逗號分隔的JMX代理列表(如使用MBeanServerFactory.findMBeanServer(String agentId)注冊的連接池。)這個屬性是僅當"jmx"屬性設置為"true"才有效。所有注冊jmx服務器使用這個屬性是不確定的
jndi-name: 數據源的名稱
maximum-active-time: 如果housekeeper 檢測到某個線程的活動時間大于這個數值.它將會殺掉這個線程.所以確認一下你的服務器的帶寬.然后定一個合適的值.默認是5分鐘.
maximum-connection-count: 最大的數據庫連接數.
maximum-connection-lifetime: 一個線程的最大壽命.
minimum-connection-count: 最小的數據庫連接數
overload-without-refusal-lifetime: 這可以幫助我們確定連接池的狀態。如果我們已經拒絕了一個連接在這個設定值(毫秒),然后被認為是超載。默認為60秒。
prototype-count: 連接池中可用的連接數量.如果當前的連接池中的連接少于這個數值.新的連接將被建立(假設沒有超過最大可用數).例如.我們有3個活動連接2個可用連接,而我們的prototype-count是4,那么數據庫連接池將試圖建立另外2個連接.這和 minimum-connection-count不同. minimum-connection-count把活動的連接也計算在內.prototype-count 是spare connections 的數量.
recently-started-threshold: 這可以幫助我們確定連接池的狀態,連接數少還是多或超載。只要至少有一個連接已開始在此值(毫秒)內,或者有一些多余的可用連接,那么我們假設連接池是開啟的。默認為60秒
simultaneous-build-throttle: 這是我們可一次建立的最大連接數。那就是新增的連接請求,但還沒有可供使用的連接。由于連接可以使用多線程,在有限的時間之間建立聯系從而帶來可用連接,但是我們需要通過一些方式確認一些線程并不是立即響應連接請求的,默認是10。
statistics: 連接池使用狀況統計。 參數“10s,1m,1d”
statistics-log-level: 日志統計跟蹤類型。 參數“ERROR”或 “INFO”
test-before-use: 如果為true,在每個連接被測試前都會服務這個連接,如果一個連接失敗,那么將被丟棄,另一個連接將會被處理,如果所有連接都失敗,一個新的連接將會被建立。否則將會拋出一個SQLException異常。
test-after-use: 如果為true,在每個連接被測試后都會服務這個連接,使其回到連接池中,如果連接失敗,那么將被廢棄。
trace: 如果為true,那么每個被執行的SQL語句將會在執行期被log記錄(DEBUG LEVEL).你也可以注冊一個ConnectionListener (參看ProxoolFacade)得到這些信息.
verbose: 詳細信息設置。 參數 bool 值
1、Metal風格 (默認)
String lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
2、Windows風格
String lookAndFeel = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
3、Windows Classic風格
String lookAndFeel = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
4、Motif風格
String lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
5、Mac風格 (需要在相關的操作系統上方可實現)
String lookAndFeel = "com.sun.java.swing.plaf.mac.MacLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
6、GTK風格 (需要在相關的操作系統上方可實現)
String lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
UIManager.setLookAndFeel(lookAndFeel);
7、可跨平臺的默認風格
String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
UIManager.setLookAndFeel(lookAndFeel);
8、當前系統的風格
String lookAndFeel = UIManager.getSystemLookAndFeelClassName();
UIManager.setLookAndFeel(lookAndFeel);
在Java中讓用戶能夠動態地更改應用的外觀,可以給用戶更好地體驗,具體的實現方式是:
1,先使用UIManager.setLookAndFeel(String s)方法設定對應的外觀
2,再使用SwingUtilities.updateComponentTreeUI(Component c)方法立刻更新應用的外觀
這兩個類均在javax.swing包中
相關文章博客:睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬
Java多線程支持需要我們不斷的進行相關問題的解決,下面我們就來看看在接口問題上的相關問題解決方案,這樣才能更好的進行不斷的干洗機創新和學習。希望大家有所了解。
Java多線程支持,所有實現Runnable接口的類都可被啟動一個新線程,新線程會執行該實例的run()方法,當run()方法執行完畢后,線程就結束了。一旦一個線程執行完畢,這個實例就不能再重新啟動,只能重新生成一個新實例,再啟動一個新線程。
Thread類是實現了Runnable接口的一個實例,它代表一個線程的實例,并且,啟動線程的唯一方法就是通過Thread類的start()實例方法:
1.Thread t = new Thread();
2.t.start();
start()方法是一個native方法,它將啟動一個新線程,并執行run()方法。Thread類默認的run()方法什么也不做就退出了。注意:直接調用run()方法并不會啟動一個新線程,它和調用一個普通的java多線程支持方法沒有什么區別。
因此,有兩個方法可以實現自己的線程:
方法1:自己的類extend Thread,并復寫run()方法,就可以啟動新線程并執行自己定義的run()方法。例如:
3.public class MyThread extends Thread {
4.public run() {
5.System.out.println("MyThread.run()");
6.}
7.}
在合適的地方啟動線程:new MyThread().start();
方法2:如果自己的類已經extends另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口:
8.public class MyThread extends OtherClass implements Runnable {
9.public run() {
10.System.out.println("MyThread.run()");
11.}
12.}
為了啟動MyThread,需要首先實例化一個Thread,并傳入自己的MyThread實例:
13.MyThread myt = new MyThread();
14.Thread t = new Thread(myt);
15.t.start();
事實上,當傳入一個Runnable target參數給Thread后,Thread的run()方法就會調用target.run(),參考JDK源代碼:
16.public void run() {
17.if (target != null) {
18.target.run();
19.}
20.}
Java多線程支持還有一些Name, ThreadGroup, isDaemon等設置,由于和線程設計模式關聯很少,這里就不多說了。
Java多線程特性為構建高性能的應用提供了極大的方便,但是也帶來了不少的麻煩。線程間同步、數據一致性等煩瑣的問題需要細心的考慮,一不小心就會出現一些微妙的,難以調試的錯誤。
另外,應用邏輯和線程邏輯糾纏在一起,會導致程序的邏輯結構混亂,難以復用和維護。本文試圖給出一個解決這個問題的方案,通過構建一個并發模型框架(framework),使得開發多線程的應用變得容易。
基礎知識
Java語言提供了對于線程很好的支持,實現方法小巧、優雅。對于方法重入的保護,信號量(semaphore)和臨界區(critical section)機制的實現都非常簡潔。可以很容易的實現多線程間的同步操作從而保護關鍵數據的一致性。這些特點使得Java成為面向對象語言中對于多線程特性支持方面的佼佼者(C++正在試圖把boost庫中的對于線程的支持部分納入語言標準)。
Java中內置了對于對象并發訪問的支持,每一個對象都有一個監視器(monitor),同時只允許一個線程持有監視器從而進行對對象的訪問,那些沒有獲得監視器的線程必須等待直到持有監視器的線程釋放監視器。對象通過synchronized關鍵字來聲明線程必須獲得監視器才能進行對自己的訪問。
synchronized聲明僅僅對于一些較為簡單的線程間同步問題比較有效,對于哪些復雜的同步問題,比如帶有條件的同步問題,Java提供了另外的解決方法,wait/notify/notifyAll。
獲得對象監視器的線程可以通過調用該對象的wait方法主動釋放監視器,等待在該對象的線程等待隊列上,此時其他線程可以得到中頻點焊機監視器從而訪問該對象,之后可以通過調用notify/notifyAll方法來喚醒先前因調用wait方法而等待的線程。
一般情況下,對于wait/notify/notifyAll方法的調用都是根據一定的條件來進行的,比如:經典的生產者/消費者問題中對于隊列空、滿的判斷。熟悉POSIX的讀者會發現,使用wait/notify/notifyAll可以很容易的實現POSIX中的一個線程間的高級同步技術:條件變量。
簡單例子
本文將圍繞一個簡單的例子展開論述,這樣可以更容易突出我們解決問題的思路、方法。本文想向讀者展現的正是這些思路、方法。這些思路、方法更加適用于解決大規模、復雜應用中的并發問題。考慮一個簡單的例子,我們有一個服務提供者,它通過一個接口對外提供服務,服務內容非常簡單,就是在標準輸出上打印Hello World。類結構圖如下:

代碼如下:
1.interface Service
2.{
3. public void sayHello();
4.}
5.class ServiceImp implements Service
6.{
7. public void sayHello() {
8. System.out.println("Hello World!");
9. }
10.}
11.class Client
12.{
13. public Client(Service s) {
14. _service = s;
15.}
16. public void requestService() {
17. _service.sayHello();
18. }
19. private Service _service;
20.}
如果現在有新的需求,要求該服務必須支持Client的并發訪問。一種簡單的方法就是在ServicImp類中的每個方法前面加上synchronized聲明,來保證自己內部數據的一致性(當然對于本例來說,目前是沒有必要的,因為ServiceImp沒有需要保護的數據,但是隨著需求的變化,以后可能會有的)。但是這樣做至少會存在以下幾個問題:
1.現在要維護ServiceImp的兩個版本:多線程版本和單線程版本(有些地方,比如其他項目,可能沒有并發的問題),容易帶來點凸焊機同步更新和正確選擇版本的問題,給維護帶來麻煩。
2.如果多個并發的Client頻繁調用該服務,由于是直接同步調用,會造成Client阻塞,降低服務質量。
3.很難進行一些靈活的控制,比如:根據Client的優先級進行排隊等等。
4.這些問題對于大型的多線程應用服務器尤為突出,對于一些簡單的應用(如本文中的例子)可能根本不用考慮。本文正是要討論這些問題的解決方案,文中的簡單的例子只是提供了一個說明問題,展示思路、方法的平臺。
5.如何才能較好的解決這些問題,有沒有一個可以重用的解決方案呢?讓我們先把這些問題放一放,先來談談和框架有關的一些問題。
框架概述
熟悉面向對象的讀者一定知道面向對象的最大的優勢之一就是:軟件復用。通過復用,可以減少很多的工作量,提高軟件開發生產率。復用本身也是分層次的,代碼級的復用和設計架構的復用。
大家可能非常熟悉C語言中的一些標準庫,它們提供了一些通用的功能讓你的程序使用。但是這些標準庫并不能影響你的程序結構和設計思路,僅僅是提供一些機能,幫助你的程序完成工作。它們使你不必重頭編寫一般性的通用功能(比如printf),它們強調的是程序代碼本身的復用性,而不是設計架構的復用性。
那么什么是框架呢?所謂框架,它不同于一般的標準庫,是指一組緊密關聯的(類)classes,強調彼此的配合以完成某種可以重復運用的設計概念。這些類之間以特定的方式合作,彼此不可或缺。它們相當程度的影響了你的程序的形貌。框架本身規劃了應用程序的骨干,讓程序遵循一定的流程和動線,展現一定的風貌和功能。這樣就使程序員不必費力于通用性的功能的繁文縟節,集中精力于專業領域。
有一點必須要強調,放之四海而皆準的框架是不存在的,也是最沒有用處的。框架往往都是針對某個特定應用領域的,是在對這個應用領域進行深刻理解的基礎上,抽象出該應用的概念模型,在這些抽象的概念上搭建的一個模型,是一個有形無體的框架。不同的具體應用根據自身的特點對框架中的抽象概念進行實現,從而賦予框架生命,完成應用的功能。
基于框架的應用都有兩部分構成:框架部分和特定應用部分。要想達到框架復用的目標,必須要做到框架部分和特定應用部分的隔離。使用面向對象的一個強大功能:多態,可以實現這一點。在框架中完成抽象概念之間的交互、關聯,把具體的實現交給特定的應用來完成。其中一般都會大量使用了Template Method設計模式。Java中的Collection Framework以及微軟的MFC都是框架方面很好的例子。有興趣的讀者可以自行研究。
構建框架
如何構建一個Java并發模型框架呢?讓我們先回到原來的問題,先來分析一下原因。造成要維護多線程和單線程兩個版本的原因是由于把應用邏輯和并發邏輯混在一起,如果能夠做到把應用邏輯和并發模型進行很好的隔離,那么應用邏輯本身就可以很好的被復用,而且也很容易把并發邏輯添加進來而不會對應用邏輯造成任何影響。造成Client阻塞,性能降低以及無法進行額外的控制的原因是由于所有的服務調用都是同步的,解決方案很簡單,改為異步調用方式,把服務的調用和服務的執行分離。
首先來介紹一個概念,活動對象(Active Object)。所謂活動對象是相對于被動對象(passive object)而言的,被動對象的方法的調用和執行都是在同一個線程中的,被動對象方法的調用是同步的、阻塞的,一般的對象都屬于被動對象;主動對象的方法的調用和執行是分離的,主動對象有自己獨立的執行線程,主動對象的上海保潔公司方法的調用是由其他線程發起的,但是方法是在自己的線程中執行的,主動對象方法的調用是異步的,非阻塞的。
本框架的核心就是使用主動對象來封裝并發邏輯,然后把Client的請求轉發給實際的服務提供者(應用邏輯),這樣無論是Client還是實際的服務提供者都不用關心并發的存在,不用考慮并發所帶來的數據一致性問題。從而實現應用邏輯和并發邏輯的隔離,服務調用和服務執行的隔離。下面給出關鍵的實現細節。
本框架有如下幾部分構成:
1.一個ActiveObject類,從Thread繼承,封裝了并發邏輯的活動對象;
2.一個ActiveQueue類,主要用來存放調用者請求;
3.一個MethodRequest接口,主要用來封裝調用者的請求,Command設計模式的一種實現方式。它們的一個簡單的實現如下:
1. //MethodRequest接口定義
2. interface MethodRequest
3.{
4. public void call();
5.}
6.//ActiveQueue定義,其實就是一個producer/consumer隊列
7. class ActiveQueue
8.{
9. public ActiveQueue() {
10. _queue = new Stack();
11. }
12. public synchronized void enqueue(MethodRequest mr) {
13. while(_queue.size() > QUEUE_SIZE) {
14. try {
15. wait();
16. }catch (InterruptedException e) {
17. e.printStackTrace();
18. }
19. }
20.
21. _queue.push(mr);
22. notifyAll();
23. System.out.println("Leave Queue");
24. }
25. public synchronized MethodRequest dequeue() {
26. MethodRequest mr;
27.
28. while(_queue.empty()) {
29. try {
30. wait();
31. }catch (InterruptedException e) {
32. e.printStackTrace();
33. }
34. }
35. mr = (MethodRequest)_queue.pop();
36. notifyAll();
37.
38. return mr;
39. }
40. private Stack _queue;
41. private final static int QUEUE_SIZE = 20;
42.}
43.//ActiveObject的定義
44.class ActiveObject extends Thread
45.{
46. public ActiveObject() {
47. _queue = new ActiveQueue();
48. start();
49. }
50. public void enqueue(MethodRequest mr) {
51. _queue.enqueue(mr);
52. }
53. public void run() {
54. while(true) {
55. MethodRequest mr = _queue.dequeue();
56. mr.call();
57. }
58. }
59. private ActiveQueue _queue;
60.}
通過上面的代碼可以看出正是這些類相互合作完成了對并發邏輯的封裝。開發者只需要根據需要實現MethodRequest接口,另外再定義一個服務代理類提供給使用者,在服務代理者類中把服務調用者的請求轉化為MethodRequest實現,交給活動對象即可。
使用該框架,可以較好的做到應用邏輯和并發模型的分離,從而使開發者集中精力于應用領域,然后平滑的和并發模型結合起來,并且可以針對ActiveQueue定制排隊機制,比如基于優先級等。
基于框架的解決方案
本小節將使用上述的框架重新實現前面的例子,提供對于并發的支持。第一步先完成對于MethodRequest的實現,對于我們的例子來說實現如下:
1.class SayHello implements MethodRequest
2.{
3. public SayHello(Service s) {
4. _service = s;
5. }
6. public void call() {
7. _service.sayHello();
8. }
9. private Service _service;
10.}
該類完成了對于服務提供接口sayHello方法的封裝。接下來定義一個服務代理類,來完成請求的封裝、排隊功能,當然為了做到對Client透明,該類必須實現Service接口。定義如下:
11.class ServiceProxy implements Service
12.{
13. public ServiceProxy() {
14. _service = new ServiceImp();
15. _active_object = new ActiveObject();
16. }
17.
18. public void sayHello() {
19. MethodRequest mr = new SayHello(_service);
20. _active_object.enqueue(mr);
21. }
22. private Service _service;
23. private ActiveObject _active_object;
24.}
其他的類和接口定義不變,下面對比一下并發邏輯增加前后的服務調用的變化,并發邏輯增加前,對于sayHello服務的調用方法:
25.Service s = new ServiceImp();
26.Client c = new Client(s);
27.c.requestService();
并發邏輯增加后,對于sayHello服務的調用方法:
28.Service s = new ServiceProxy();
29.Client c = new Client(s);
30.c.requestService();
可以看出并發邏輯增加前后對于Client的ServiceImp都無需作任何改變,使用方式也非常一致,ServiceImp也能夠獨立的進行重用。類結構圖如下:

讀者容易看出,使用框架也增加了一些復雜性,對于一些簡單的應用來說可能根本就沒有必要使用本框架。希望讀者能夠根據自己的實際情況進行判斷。
結論
本文圍繞一個簡單的例子論述了如何構架一個Java并發模型框架,其中使用了一些構建框架的常用技術,當然所構建的框架和一些成熟的商用框架相比,顯得非常稚嫩,比如沒有考慮服務調用有返回值的情況,但是其思想方法是一致的,希望讀者能夠深加領會,這樣無論對于構建自己的框架還是理解一些其他的框架都是很有幫助的。讀者可以對本文中的框架進行擴充,直接應用到自己的工作中。
優點:
1.增強了應用的并發性,簡化了同步控制的復雜性;
2.服務的請求和服務的執行分離,使得可以對服務請求排隊,進行靈活的控制;
3.應用邏輯和并發模型分離,使得程序結構清晰,易于維護、重用;
4.可以使開發者集中精力于應用領域。
缺點:
1.由于框架所需類的存在,在一定程度上增加了程序的復雜性;
2.如果應用需要過多的活動對象,由于線程切換開銷會造成性能下降;
3.可能會造成調試困難。
在Java語言產生前,傳統的程序設計語言的程序同一時刻只能單任務操作,效率非常低,例如程序往往在接收數據輸入時發生阻塞,只有等到程序獲得數據后才能繼續運行。隨著Internet的迅猛發展,這種狀況越來越不能讓人們忍受:如果網絡接收熱合機數據阻塞,后臺程序就處于等待狀態而不繼續任何操作,而這種阻塞是經常會碰到的,此時CPU資源被白白的閑置起來。如果在后臺程序中能夠同時處理多個任務,該多好啊!應Internet技術而生的Java語言解決了這個問題,多線程程序是Java語言的一個很重要的特點。在一個Java程序中,我們可以同時并行運行多個相對獨立的線程,例如,我們如果創建一個線程來進行數據輸入輸出,而創建另一個線程在后臺進行其它的數據處理,如果輸入輸出線程在接收數據時阻塞,而處理數據的線程仍然在運行。多線程程序設計大大提高了程序執行效率和處理能力。
線程的創建
我們知道Java是面向對象的程序語言,用Java進行程序設計就是設計和使用類,Java為我們提供了線程類Thread來創建線程,創建線程與創建普通的類的對象的操作是一樣的,而線程就是Thread類或其子類的實例對象。下面是一個創建啟動一個線程的語句:
Thread thread1=new Thread(); file://聲明一個對象實例,即創建一個線程;
Thread1.run(); file://用Thread類中的run()方法啟動線程;
從這個例子,我們可以通過Thread()構造方法創建一個線程,并啟動該線程。事實上,啟動線程,也就是啟動線程的run()方法,而Thread類中的run()方法沒有任何操作語句,所以這個線程沒有任何操作。要使線程實現預定功能,必須定義自己的run()方法。Java中通常有兩種方式定義 run()方法:
通過定義一個Thread類的子類,在該子類中重寫run()方法。Thread子類的實例對象就是一個線程,顯然,該線程有我們自己設計的線程體run()方法,啟動線程就啟動了子類中重寫的run()方法。
通過Runnable接口,在該接口中定義run()方法的接口。所謂接口跟類非常類似,主要用來實現特殊功能,如復雜關系的多重繼承功能。在此,我們定義一個實現Runnable() 接口的類,在該類中定義自己的run()方法,然后以該類的實例對象為參數調用Thread類的構造方法來創建一個線程。
線程被實際創建后處于待命狀態,激活(啟動)線程就是啟動線程的run()方法,這是通過調用線程的start()方法來實現的。
下面一個例子實踐了如何通過上述兩種方法創建線程并啟動它們:
// 通過Thread類的子類創建的線程;
{ file://自定義線程的run()方法;
file://通過Runnable接口創建的另外一個線程;
{ file://自定義線程的run()方法;
file://程序的主類'
class Multi_Thread file://聲明主類;
plubic static void mail(String args[]) file://聲明主方法;
thread1 threadone=new thread1(); file://用Thread類的子類創建線程;
Thread threadtwo=new Thread(new thread2()); file://用Runnable接口類的對象創建線程;
threadone.start(); threadtwo.start(); file://strat()方法啟動線程;
運行該程序就可以看出,線程threadone和threadtwo交替占用CPU,處于并行運行狀態。可以看出,啟動線程的run()方法是通過調用線程的start()方法來實現的(見上例中主類),調用start()方法啟動線程的run()方法不同于一般的調用方法,調用一般方法時,必須等到一般方法執行完畢才能夠返回start()方法,而啟動線程的run()方法后,start()告訴系統該線程準備就緒可以啟動run()方法后,就返回 start()方法執行調用start()方法語句下面的語句,這時run()方法可能還在運行,這樣,線程的啟動和運行并行進行,實現了多任務操作。
線程的優先級
對于多線程程序,每個線程的重要程度是不盡相同,如多個線程在等待獲得CPU時間時,往往我們需要優先級高的線程優先搶占到CPU時間得以執行;又如多個線程交替執行時,優先級決定了級別高的線程得到CPU的次數多一些且時間多長一些;這樣,高優先級的線程處理的任務效率就高一些。
Java中線程的優先級從低到高以整數1~10表示,共分為10級,設置優先級是通過調用線程對象的setPriority()方法,如上例中,設置優先級的語句為:
thread1 threadone=new thread1(); file://用Thread類的子類創建線程;
Thread threadtwo=new Thread(new thread2()); file://用Runnable接口類的對象創建線程;
threadone.setPriority(6); file://設置threadone的優先級6;
threadtwo.setPriority(3); file://設置threadtwo的優先級3;
threadone.start(); threadtwo.start(); file://strat()方法啟動線程;
這樣,線程threadone將會優先于線程threadtwo執行,并將占有更多的CPU時間。該例中,優先級設置放在線程啟動前,也可以在啟動后進行設置,以滿足不同的優先級需求。
線程的(同步)控制
一個Java程序的多線程之間可以共享數據。當線程以異步方式訪問共享數據時,有時候是不安全的或者不和邏輯的。比如,同一時刻一個線程在讀取數據,另外一個線程在處理數據,當處理數據的線程沒有等到讀取收縮機數據的線程讀取完畢就去處理數據,必然得到錯誤的處理結果。這和我們前面提到的讀取數據和處理數據并行多任務并不矛盾,這兒指的是處理數據的線程不能處理當前還沒有讀取結束的數據,但是可以處理其它的數據。
如果我們采用多線程同步控制機制,等到第一個線程讀取完數據,第二個線程才能處理該數據,就會避免錯誤。可見,線程同步是多線程編程的一個相當重要的技術。
在講線程的同步控制前我們需要交代如下概念:
1 用Java關鍵字synchonized同步對共享數據操作的方法
在一個對象中,用synchonized聲明的方法為同步方法。Java中有一個同步模型-監視器,負責管理線程對對象中的同步方法的訪問,它的原理是:賦予該對象唯一一把'鑰匙',當多個線程進入對象,只有取得該對象鑰匙的線程才可以訪問同步方法,其它線程在該對象中等待,直到該線程用wait() 方法放棄這把鑰匙,其它等待的線程搶占該鑰匙,搶占到鑰匙的線程后才可得以執行,而沒有取得鑰匙的線程仍被阻塞在該對象中等待。
file://聲明同步的一種方式:將方法聲明同步
class store
public synchonized void store_in()
public synchonized void store_out(){
2 利用wait()、notify()及notifyAll()方法發送消息實現線程間的相互聯系
Java程序中多個線程通過消息來實現互動聯系的,這幾種方法實現了線程間的消息發送。例如定義一個對象的synchonized 方法,同一時刻只能夠有一個線程訪問該對象中的同步方法,其它線程被阻塞。通常可以用notify()或notifyAll()方法喚醒其它一個或所有線程。而使用wait()方法來使該線程處于阻塞狀態,等待其它的線程用notify()喚醒。
一個實際的例子就是生產和銷售,生產單元將產品生產出來放在倉庫中,銷售單元則從倉庫中提走產品,在這個過程中,銷售單元必須在倉庫中有產品時才能提貨;如果倉庫中沒有產品,則銷售單元必須等待。
程序中,假如我們定義一個倉庫類store,該類的實例對象就相當于倉庫,在store類中定義兩個成員方法:store_in(),用來模擬產品制造者往倉庫中添加產品;strore_out()方法則用來模擬銷售者從倉庫中取走產品。然后定義兩個線程類:customer類,其中的run()方法通過調用倉庫類中的store_out()從倉庫中取走產品,模擬銷售者;另外一個線程類producer中的run()方法通過調用倉庫類中的 store_in()方法向倉庫添加產品,模擬產品制造者。在主類中創建并啟動線程,實現向倉庫中添加產品或取走產品。
如果倉庫類中的store_in() 和store_out()方法不聲明同步,這就是個一般的多線程,我們知道,一個程序中的多線程是交替執行的,運行也是無序的,這樣,就可能存在這樣的問題:
倉庫中沒有產品了,銷售者還在不斷光顧,而且還不停的在'取'產品,這在現實中是不可思義的,在程序中就表現為負值;如果將倉庫類中的stroe_in ()和store_out()方法聲明同步,如上例所示:就控制了同一時刻只能有一個線程訪問倉庫對象中的同步方法;即一個生產類線程訪問被聲明為同步的 store_in()方法時,其它線程將不能夠訪問對象中的store_out()同步方法,當然也不能訪問store_in()方法。必須等到該線程調用wait()方法放棄鑰匙,其它線程才有機會訪問同步方法。
Java代碼
1.package com.zbalpha.test;
2.
3.import java.util.ArrayList;
4.import java.util.Iterator;
5.import java.util.List;
6.
7.public class ListTest {
8. public static void main(String args[]){
9. List<Long> lists = new ArrayList<Long>();
10.
11. for(Long i=0l;i<1000000l;i++){
12. lists.add(i);
13. }
14.
15. Long oneOk = oneMethod(lists);
16. Long twoOk = twoMethod(lists);
17. Long threeOk = threeMethod(lists);
18. Long fourOk = fourMethod(lists);
19.
20. System.out.println("One:" + oneOk);
21. System.out.println("Two:" + twoOk);
22. System.out.println("Three:" + threeOk);
23. System.out.println("four:" + fourOk);
24.
25. }
26.
27. public static Long oneMethod(List<Long> lists){
28.
29. Long timeStart = System.currentTimeMillis();
30. for(int i=0;i<lists.size();i++) {
31. System.out.println(lists.get(i));
32. }
33. Long timeStop = System.currentTimeMillis();
34.
35. return timeStop -timeStart ;
36. }
37.
38. public static Long twoMethod(List<Long> lists){
39.
40. Long timeStart = System.currentTimeMillis();
41. for(Long string : lists) {
42. System.out.println(string);
43. }
44. Long timeStop = System.currentTimeMillis();
45.
46. return timeStop -timeStart ;
47. }
48.
49. public static Long threeMethod(List<Long> lists){
50.
51. Long timeStart = System.currentTimeMillis();
52. Iterator<Long> it = lists.iterator();
53. while (it.hasNext())
54. {
55. System.out.println(it.next());
56. }
57. Long timeStop = System.currentTimeMillis();
58.
59. return timeStop -timeStart ;
60. }
61.
62.
63.
64. public static Long fourMethod(List<Long> lists){
65.
66. Long timeStart = System.currentTimeMillis();
67. for(Iterator<Long> i = lists.iterator(); i.hasNext();) {
68. System.out.println(i.next());
69. }
70. Long timeStop = System.currentTimeMillis();
71.
72. return timeStop -timeStart ;
73. }
74.}
容器類可以大大提高編程效率和編程能力,在Java2中,所有的容器都由SUN公司的Joshua Bloch進行了重新設計,豐富了容器類庫的功能。
Java2容器類類庫的用途是“保存對象”,它分為兩類:
Collection----一組獨立的元素,通常這些元素都服從某種規則。List必須保持元素特定的順序,而Set不能有重復元素。
Map----一組成對的“鍵值對”對象,即其元素是成對的對象,最典型的應用就是數據字典,并且還有其它廣泛的應用。另外,Map可以返回其所有鍵豐胸組成的Set和其所有值組成的Collection,或其鍵值對組成的Set,并且還可以像數組一樣擴展多維Map,只要讓Map中鍵值對的每個 “值”是一個Map即可。
1.迭代器
迭代器是一種設計模式,它是一個對象,它可以遍歷并選擇序列中的對象,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”對象,因為創建它的代價小。
Java中的Iterator功能比較簡單,并且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。
(2) 使用next()獲得序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。
2.List的功能方法
List(interface): 次序是List最重要的特點;它確保維護元素特定的順序。List為Collection添加了許多方法,使得能夠向List中間插入與移除元素(只推薦 LinkedList使用)。一個List可以生成ListIterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和刪除元素。
ArrayList: 由數組實現的List。它允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。ListIterator只應該用來由后向前遍歷ArrayList,而不是用來插入和刪除元素,因為這比LinkedList開銷要大很多。
LinkedList: 對順序訪問進行了優化,向List中間插入與刪除得開銷不大,隨機訪問則相對較慢(可用ArrayList代替)。它具有方法addFirst()、 addLast()、getFirst()、getLast()、removeFirst()、removeLast(),這些方法(沒有在任何接口或基類中定義過)使得LinkedList可以當作堆棧、隊列和雙向隊列使用。
3.Set的功能方法
Set(interface): 存入Set的每個元素必須是唯一的,因為Set不保存重復元素。加入Set的Object必須定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。
HashSet: 為快速查找而設計的Set。存入HashSet的對象必須定義hashCode()。
TreeSet: 保持次序的Set,底層為樹結構。使用它可以從Set中提取有序的序列。
LinkedHashSet: 具有HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。于是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。
HashSet采用散列函數對元素進行排序,這是專門為快速查詢而設計的;TreeSet采用紅黑樹的數據結構進行排序元素;LinkedHashSet內部使用散列以加快查詢速度,同時使用鏈表維護元素的次序,使得看起來元素是以插入的順序保存的。需要注意的是,生成自己的類時,Set需要維護元素的存儲順序,因此要實現Comparable接口并定義compareTo()方法。
在java編程思想中對synchronized的一點解釋:
1、synchronized關鍵字的作用域有二種:
1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法;
2)是某個類的范圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。
2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用于方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前對象;
3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中并不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法;
----------------------------------------------------------------------------
java里面synchronized用法
synchronized的一個簡單例子
public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自動生成方法存根
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
synchronized(str)
{
if (num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.getMessage();
}
System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
}
}
}
}
}
上面的例子中為了制造一個時間差,也就是出錯的機會,使用了Thread.sleep(10)
Java對多線程的支持與衛星電視同步機制深受大家的喜愛,似乎看起來使用了synchronized關鍵字就可以輕松地解決多線程共享數據同步問題。到底如何?――還得對synchronized關鍵字的作用進行深入了解才可定論。
總的說來,synchronized關鍵字可以作為函數的修飾符,也可作為函數內的語句,也就是平時說的同步方法和同步語句塊。如果再細的分類,synchronized可作用于instance變量、object reference(對象引用)、static函數和class literals(類名稱字面常量)身上。
在進一步闡述之前,我們需要明確幾點:
A.無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問。
B.每個對象只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
接著來討論synchronized用到不同地方對代碼產生的影響:
假設P1、P2是同一個類的不同對象,這個類中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以調用它們。
1. 把synchronized當作函數修飾符時,示例代碼如下:
Public synchronized void methodAAA()
{
//….
}
這也就是同步方法,那這時synchronized鎖定的是哪個對象呢?它鎖定的是調用這個同步方法對象。也就是說,當一個對象P1在不同的線程中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個對象所屬的Class所產生的另一對象P2卻可以任意調用這個被加了synchronized關鍵字的方法。
上邊的示例代碼等同于如下代碼:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)處的this指的是什么呢?它指的就是調用這個方法的對象,如P1。可見同步方法實質是將synchronized作用于object reference。――那個拿到了P1對象鎖的線程,才可以調用P1的同步方法,而對P2而言,P1這個鎖與它毫不相干,程序也可能在這種情形下擺脫同步機制的控制,造成數據混亂:(
2.同步塊,示例代碼如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
這時,鎖就是so這個對象,誰拿到這個鎖誰就可以運行它所控制的那段代碼。當有一個明確的對象作為鎖時,就可以這樣寫程序,但當沒有明確的對象作為鎖,只是想讓一段代碼同步時,可以創建一個特殊的instance變量(它得是一個對象)來充當鎖:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯后的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
3.將synchronized作用于static 函數,示例代碼如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函數
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(類名稱字面常量)
}
}
代碼中的methodBBB()方法是把class literal作為鎖的情況,它和同步的static函數產生的效果是一樣的,取得的鎖很特別,是當前調用這個方法的衛星電視對象所屬的類(Class,而不再是由這個Class產生的某個具體對象了)。
記得在《Effective Java》一書中看到過將 Foo.class和 P1.getClass()用于作同步鎖還不一樣,不能用P1.getClass()來達到鎖這個Class的目的。P1指的是由Foo類產生的對象。
可以推斷:如果一個類中定義了一個synchronized的static函數A,也定義了一個synchronized 的instance函數B,那么這個類的同一對象Obj在多線程中分別訪問A和B兩個方法時,不會構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個對象,而B的鎖是Obj所屬的那個Class。
小結如下:
搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序。
還有一些技巧可以讓我們對共享資源的同步訪問更加安全:
1. 定義private 的instance變量+它的 get方法,而不要定義public/protected的instance變量。如果將變量定義為public,對象在外界可以繞過同步方法的控制而直接取得它,并改動它。這也是JavaBean的標準實現方式之一。
2. 如果instance變量是一個對象,如數組或ArrayList什么的,那上述方法仍然不安全,因為當外界對象通過get方法拿到這個instance對象的引用后,又將其指向另一個對象,那么這個private變量也就變了,豈不是很危險。 這個時候就需要將get方法也加上synchronized同步,并且,只返回這個private對象的clone()――這樣,調用端得到的就是對象副本的引用了。
JDK 5.0 中增加的泛型類型,是 Java 語言中類型安全的一次重要改進。但是,對于初次使用泛型類型的用戶來說,泛型的某些方面看起來可能不容易明白,甚至非常奇怪。在本月的“Java 理論和實踐”中,Brian Goetz 分析了束縛第一次使用泛型的用戶的常見陷阱。您可以通過討論論壇與作者和其他讀者分享您對本文的看法。(也可以單擊本文頂端或底端的討論來訪問這個論壇。)
表面上看起來,無論語法還是應用的環境(比如容器類),泛型類型(或者泛型)都類似于 C++ 中的模板。但是這種相似性僅限于表面,Java 語言中的泛型基本上完全在編譯器中實現,由編譯器執行類型檢查和類型推斷,然后生成普通的非泛型的字節碼。這種實現技術稱為擦除(erasure)(編譯器使用泛型類型信息保證類型安全,然后在生成字節碼之前將其清除),這項技術有一些奇怪,并且有時會帶來一些令人迷惑的后果。雖然范型是 Java 類走向類型安全的一大步,但是在學習使用泛型的過程中幾乎肯定會遇到頭痛(有時候讓人無法忍受)的問題。
注意:本文假設您對 JDK 5.0 中的范型有基本的了解。
泛型不是協變的
雖然將集合看作是數組的抽象會有所幫助,但是數組還有一些集合不具備的特殊性質。Java 語言中的數組是協變的(covariant),也就是說,如果 Integer 擴展了 Number(事實也是如此),那么不僅 Integer 是 Number,而且 Integer[] 也是 Number[],在要求 Number[] 的地方完全可以傳遞或者賦予 Integer[]。(更正式地說,如果 Number 是 Integer 的超類型,那么 Number[] 也是 Integer[] 的超類型)。您也許認為這一原理同樣適用于泛型類型 —— List<Number> 是 List<Integer> 的超類型,那么可以在需要 List<Number> 的地方傳遞 List<Integer>。不幸的是,情況并非如此。
不允許這樣做有一個很充分的理由:這樣做將破壞要提供的類型安全泛型。如果能夠將 List<Integer> 賦給 List<Number>。那么下面的代碼就允許將非 Integer 的內容放入 List<Integer>:
List<Integer> li = new ArrayList<Integer nike jordan 2010>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
因為 ln 是 List<Number>,所以向其添加 Float 似乎是完全合法的。但是如果 ln 是 li 的別名,那么這就破壞了蘊含在 li 定義中的類型安全承諾 —— 它是一個整數列表,這就是泛型類型不能協變的原因。
其他的協變問題
數組能夠協變而泛型不能協變的另一個后果是,不能實例化泛型類型的數組(new List<String>[3] 是不合法的),除非類型參數是一個未綁定的通配符(new List<?>[3] 是合法的)。讓我們看看如果允許聲明泛型類型數組會造成什么后果:
List<String>[] lsa = new List<String>[10]; // illegal
Object[] oa = lsa; // OK because List<String> is a subtype of Object
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[0] = li;
String s = lsa[0].get(0);
最后一行將拋出 ClassCastException,因為這樣將把 List<Integer> 填入本應是 List<String> 的位置。因為數組協變會破壞泛型的類型安全,所以不允許實例化泛型類型的數組(除非類型參數是未綁定的通配符,比如 List<?>)。
構造延遲
因為可以擦除功能,所以 List<Integer> 和 List<String> 是同一個類,編譯器在編譯 List<V> 時只生成一個類(和 C++ 不同)。因此,在編譯 List<V> 類時,編譯器不知道 V 所表示的類型,所以它就不能像知道類所表示的具體類型那樣處理 List<V> 類定義中的類型參數(List<V> 中的 V)。
因為運行時不能區分 List<String> 和 List<Integer>(運行時都是 List),用泛型類型參數標識類型的變量的構造就成了問題。運行時缺乏類型信息,這給泛型容器類和希望創建保護性副本的泛型類提出了難題。
比如泛型類 Foo:
class Foo<T> {
public void doSomething(T param) { ... }
}
在這里可以看到一種模式 —— 與泛型有關的很多問題或者折衷并非來自泛型本身,而是保持和已有代碼兼容的要求帶來的副作用。
泛化已有的類
在轉化現有的庫類來使用泛型方面沒有多少技巧,但與平常的情況相同,向后兼容性不會憑空而來。我已經討論了兩個例子,其中向后兼容性限制了類庫的泛化。
另一種不同的泛化方法可能不存在向后兼容問題,這就是 Collections.toArray nike shox r4(Object[])。傳入 toArray() 的數組有兩個目的 —— 如果集合足夠小,那么可以將其內容直接放在提供的數組中。否則,利用反射(reflection)創建相同類型的新數組來接受結果。如果從頭開始重寫 Collections 框架,那么很可能傳遞給 Collections.toArray() 的參數不是一個數組,而是一個類文字:
interface Collection<E> {
public T[] toArray(Class<T super E> elementClass);
}
因為 Collections 框架作為良好類設計的例子被廣泛效仿,但是它的設計受到向后兼容性約束,所以這些地方值得您注意,不要盲目效仿。
首先,常常被混淆的泛型 Collections API 的一個重要方面是 containsAll()、removeAll() 和 retainAll() 的簽名。您可能認為 remove() 和 removeAll() 的簽名應該是:
interface Collection<E> {
public boolean remove(E e); // not really
public void removeAll(Collection<? extends E> c); // not really
}
但實際上卻是:
interface Collection<E> {
public boolean remove(Object o);
public void removeAll(Collection<?> c);
}
為什么呢?答案同樣是因為向后兼容性。x.remove(o) 的接口表明“如果 o 包含在 x 中,則刪除它,否則什么也不做。”如果 x 是一個泛型集合,那么 o 不一定與 x 的類型參數兼容。如果 removeAll() 被泛化為只有類型兼容時才能調用(Collection<? extends E>),那么在泛化之前,合法的代碼序列就會變得不合法,比如:
// a collection of Integers
Collection c = new HashSet();
// a collection of Objects
Collection r = new HashSet();
c.removeAll(r);
如果上述片段用直觀的方法泛化(將 c 設為 Collection<Integer>,r 設為 Collection<Object>),如果 removeAll() 的簽名要求其參數為 Collection<? extends E> 而不是 no-op,那么就無法編譯上面的代碼。泛型類庫的一個主要目標就是不打破或者改變已有代碼的語義,因此,必須用比從頭重新設計泛型所使用類型約束更弱的類型約束來定義 remove()、removeAll()、retainAll() 和 containsAll()。
在泛型之前設計的類可能阻礙了“顯然的”泛型化方法。這種情況下就要像上例這樣進行折衷,但是如果從頭設計新的泛型類,理解 Java 類庫中的哪些東西是向后兼容的結果很有意義,這樣可以避免不適當的模仿。
擦除的實現
因為泛型基本上都是在 Java 編譯器中而不是運行庫中實現的,所以在生成字節碼的時候,差不多所有關于泛型類型的類型信息都被“擦掉”了。換句話說,編譯器生成的代碼與您手工編寫的不用泛型、檢查程序的類型安全后進行強制類型轉換所得到的代碼基本相同。與 C++ 不同,List<Integer> 和 List<String> 是同一個類(雖然是不同的類型但都是 List<?> 的子類型,與以前的版本相比,在 JDK 5.0 中這是一個更重要的區別)。
擦除意味著一個類不能同時實現 Comparable<String> 和 Comparable<Number>,因為事實上兩者都在同一個接口中,指定同一個 compareTo() 方法。聲明 DecimalString 類以便與 String 與 Number 比較似乎是明智的,但對于 Java 編譯器來說,這相當于對同一個方法進行了兩次聲明:
public class DecimalString implements Comparable fashion cap<Number>, Comparable<String> { ... } // nope
擦除的另一個后果是,對泛型類型參數是用強制類型轉換或者 instanceof 毫無意義。下面的代碼完全不會改善代碼的類型安全性:
public <T> T naiveCast(T t, Object o) { return (T) o; }
編譯器僅僅發出一個類型未檢查轉換警告,因為它不知道這種轉換是否安全。naiveCast() 方法實際上根本不作任何轉換,T 直接被替換為 Object,與期望的相反,傳入的對象被強制轉換為 Object。
擦除也是造成上述構造問題的原因,即不能創建泛型類型的對象,因為編譯器不知道要調用什么構造函數。如果泛型類需要構造用泛型類型參數來指定類型的對象,那么構造函數應該接受類文字(Foo.class)并將它們保存起來,以便通過反射創建實例。
結束語
泛型是 Java 語言走向類型安全的一大步,但是泛型設施的設計和類庫的泛化并非未經過妥協。擴展虛擬機指令集來支持泛型被認為是無法接受的,因為這會為 Java 廠商升級其 JVM 造成難以逾越的障礙。因此采用了可以完全在編譯器中實現的擦除方法。類似地,在泛型 Java 類庫時,保持向后兼容也為類庫的泛化方式設置了很多限制,產生了一些混亂的、令人沮喪的結構(如 Array.newInstance())。這并非泛型本身的問題,而是與語言的演化與兼容有關。但這些也使得泛型學習和應用起來更讓人迷惑,更加困難。
2009年12月5日
#
在Web2.0的浪潮中,各種頁面技術和框架不斷涌現,為服務器端的基礎架構提出了更高的穩定性和可擴展性的要求。近年來,作為開源中間件的全球領導者,JBoss在J2EE應用服務器領域已成為發展最為迅速的應用服務器。在市場占有率和服務滿意度上取得了巨大的成功,絲毫不遜色于其它的非開源競爭對手,如WebSphere、WebLogic、Application Server。JBoss Web的諸多優越性能,正是其廣為流行的原因。
基于Tomcat內核,青勝于藍
Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器,技術先進、性能穩定,而且免費,因而深受Java 愛好者的喜愛并得到了部分軟件開發商的認可。其運行時占用的系統資源小,擴展性好,且支持負載平衡與郵件服務等開發應用系統常用的功能。作為一個小型的輕量級應用服務器,Tomcat在中小型系統和并發訪問用戶不是很多的場合下被普遍使用,成為目前比較流行的Web 應用服務器。
而JBoss Web采用業界最優的開源Java Web引擎, 將Java社區中下載量最大,用戶數最多,標準支持最完備的Tomcat內核作為其Servlet容器引擎,并加以審核和調優。單純的Tomcat性能有限,在很多地方表現有欠缺,如活動連接支持、靜態內容、大文件和HTTPS等。除了性能問題,Tomcat的另一大缺點是它是一個受限的集成平臺,僅能運行Java應用程序。企業在使用時Tomcat,往往還需同時部署Apache Web Server以與之整合。此配置較為繁瑣,且不能保證性能的優越性。
JBoss在Tomcat的基礎上,對其進行本地化,將Tomcat 以內嵌的方式集成到 JBoss 中。JBoss Web通過使用APR和Tomcat本地技術的混合模型來解決Tomcat的諸多不足。混合技術模型從最新的操作系統技術里提供了最好的線程和事件處理。結果,JBoss Web達到了可擴展性,性能參數匹配甚至超越了本地Apache HTTP服務器或者IIS。譬如JBoss Web能夠提供數據庫連接池服務,不僅支持 JSP 等 Java 技術,同時還支持其他 Web 技術的集成,譬如 PHP、.NET 兩大陣營。
標準化是減小技術依賴風險,保護投資最好的方式。JBoss Web率先支持全系列JEE Web標準,從根本上保證了應用“一次開發,到處運行”的特點,使應用成品能方便地在JBoss Web和其他Java Web服務器之間輕易遷移。
集多功能于一身,性能卓越
作為Web 應用服務器中的明星產品,JBoss Web服務器集多種功能于一身。其關鍵功能包括:完全支持Java EE、高度的擴展性、快速的靜態內容處理、群集、OpenSSL、URL重寫和綜合性。
JBoss Web服務器具有原生特性和強大的可擴展性,可支持多種并非基于Java的服務器內容處理技術,可同時運行JSP, Servlet, Microsoft .NET , PHP 及 CGI,為其提供一個單一的、高性能的企業級部署平臺。
與Tomcat 相比,JBoss Web在靜態資源訪問方面性能優越。JBoss Web支持兩種組件模式——純Java和Native I/O。在Native組件的支持下,動態運行不會受到任何影響,而靜態資源的訪問利用了操作系統本身提供的0拷貝傳送,CPU消耗降低,響應時間縮短,吞吐率大大提高,混合的連接模式支持最大達到10000個并發客戶端的同時訪問,與Apache Web服務器相當。部署于高性能的操作系統,可利用JBoss Web對純Java和Native I/O兩種模式的支持,使得應用在開發時可隨時跨平臺敏捷遷移,而部署于高性能的操作系統相關的Native環境。由于JBoss Web較好地解決了靜態資源的訪問性能問題,可在解決方案中把它直接作為強大的LVS的分發對象,和RHEL負載均衡系統結合,形成理論上無限線性擴展的負載均衡場景。
OpenSSL是業界最為快速和安全的開源傳輸組件,可借助操作系統和硬件的特性實現高效的安全承載。JBoss Web集成了OpenSSL,可提供高效的安全傳輸服務,使得安全機制更上臺階。研究表明, JBoss Web中的SSL性能比單純的Tomcat快四倍。
URL重寫功能可縮短URL,隱藏實際路徑提高安全性,易于用戶記憶和鍵入,及被搜索引擎收錄。Tomcat 不具備URL重寫功能,JBoss Web則可提供一個靈活的URL rewriting操作引擎,支持無限個規則數和規則條件。URL可被重寫以支持遺留的URL錯誤處理,或應對服務器不時產生的其他問題。
JBoss Web既可單獨運行,也可無縫嵌入JBoss應用服務器,成為JBoss中間件平臺的一部分。不僅后臺服務調用的性能將得以提升,也可利用以下JBoss平臺的特性提升Web應用功能:
基于JGroups的多種集群方案的支持
基于Arjuna技術的JTA和JTS的事務處理支持
優化的線程池和連接池的支持
基于JMX 控制臺的基本管理支持和JBoss On的高級管理維護支持
基于JBoss AOP技術的面向方面架構的支持
Hibernate服務組件的支持
專業團隊支持
業界大多數開源產品在技術方面富于創新性,但在可持續性,產品生命周期規劃,以及質量保證方面缺乏有效保障,為軟件集成商和最終用戶所詬病。紅帽所力行的“專業化開源技術”則完美解決了這一問題。
來自開源社區的JBoss Web,在紅帽專業化開源的錘煉下,在性能、擴展性、穩定性、安全性等方面,已成為一個達到企業級,甚至電信級標準的優秀產品。紅帽不僅有專職的技術團隊投入JBoss Web的開發,而且具備專門的QA團隊為產品作質量保證。完善的集成測試和兼容性測試保證了JBoss Web自身的穩定性,并保證了它的后向兼容和其他JBoss產品協作良好的互操作性。
在服務體系保障方面,JBoss 開拓了以產品專家提供的專家級支持服務作為開源軟件強大后盾的軟件生態模式。公司以及龐大的 JBoss 授權服務合作伙伴網絡可為包括JBoss Web在內的整個JEMS 產品套件提供全面的支持服務。與Tomcat相比,JBoss Web 可提供遷移服務與現場專家服務,在遷移服務方面,專家指導應用可從Tomcat向JBoss Web遷移,省時省力。獨特的服務訂閱模式,全力保障軟件生命周期,讓企業高枕無憂。