睫晉姬

          2009年12月5日 #

          如何獲取Sun推出的Java認證指南

            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認證考試非常嚴謹,需要你具備充足的實踐經驗才可能通過。

          posted @ 2010-07-29 16:10 睫晉姬 閱讀(263) | 評論 (0)編輯 收藏

          spring連接池配置詳解

            數據庫連接池概述:

            數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現得尤為突出。對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫連接池正是針對這個問題提出來的。

            數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而再不是重新建立一個;釋放空閑時間超過最大空閑時間的數據庫連接來避免因為沒有釋放數據庫連接而引起的數據庫連接遺漏。這項技術能明顯提高對數據庫操作的性能。

            數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量。連接池的最大數據庫連接干洗設備數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。數據庫連接池的最小連接數和最大連接數的設置要考慮到下列幾個因素:

            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 值

          posted @ 2010-07-29 16:03 睫晉姬 閱讀(561) | 評論 (0)編輯 收藏

          Java中的LookAndFeel

            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包中
          相關文章博客:睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬 睫晉姬

          posted @ 2010-07-29 16:00 睫晉姬 閱讀(255) | 評論 (0)編輯 收藏

          Java多線程支持如何才能解決接口問題

            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等設置,由于和線程設計模式關聯很少,這里就不多說了。

          posted @ 2010-07-29 15:59 睫晉姬 閱讀(308) | 評論 (0)編輯 收藏

          多線程開發的捷徑:構建Java并發模型框架

            Java多線程特性為構建高性能的應用提供了極大的方便,但是也帶來了不少的麻煩。線程間同步、數據一致性等煩瑣的問題需要細心的考慮,一不小心就會出現一些微妙的,難以調試的錯誤。

            另外,應用邏輯和線程邏輯糾纏在一起,會導致程序的邏輯結構混亂,難以復用和維護。本文試圖給出一個解決這個問題的方案,通過構建一個并發模型框架(framework),使得開發多線程的應用變得容易。

            基礎知識

            Java語言提供了對于線程很好的支持,實現方法小巧、優雅。對于方法重入的保護,信號量(semaphore)和臨界區(critical section)機制的實現都非常簡潔??梢院苋菀椎膶崿F多線程間的同步操作從而保護關鍵數據的一致性。這些特點使得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.可能會造成調試困難。

          posted @ 2010-07-29 15:55 睫晉姬 閱讀(268) | 評論 (0)編輯 收藏

          Java多線程程序設計初步

            在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()方法放棄鑰匙,其它線程才有機會訪問同步方法。

          posted @ 2010-07-29 15:50 睫晉姬 閱讀(212) | 評論 (0)編輯 收藏

          Java List遍歷方法及其效率對比

            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()方法。

          posted @ 2010-07-29 15:37 睫晉姬 閱讀(11919) | 評論 (1)編輯 收藏

          Java中synchronized用法

            在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()――這樣,調用端得到的就是對象副本的引用了。

          posted @ 2010-07-29 15:31 睫晉姬 閱讀(208) | 評論 (0)編輯 收藏

          JDK 5.0中的泛型類型學習

            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())。這并非泛型本身的問題,而是與語言的演化與兼容有關。但這些也使得泛型學習和應用起來更讓人迷惑,更加困難。

          posted @ 2010-07-29 15:23 睫晉姬 閱讀(219) | 評論 (0)編輯 收藏

          JBoss Web和Tomcat的區別

            在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遷移,省時省力。獨特的服務訂閱模式,全力保障軟件生命周期,讓企業高枕無憂。

          posted @ 2009-12-05 17:46 睫晉姬 閱讀(306) | 評論 (0)編輯 收藏

          Tomcat5集群中的SESSION復制

          Tomcat5集群中的SESSION復制

              Tomcat 5服務器為集群和SESSION復制提供了集成的支持。本系列的第一篇文章將為大家提供SESSION持久性以及TOMCAT集群中SESSION復制的內在工作機制一個概要認識。我將會討論SESSION復制在TOMCAT5中是怎樣進行的以及跨越多集群節點的SESSION持久性的復制機制。在第2部分,我會詳細討論一個帶有SESSION復制功能的TOMCAT集群的安裝例子,并且比較不同的復制情形。

              集群

              傳統獨立服務器(非集群的)不提供任何失效無縫轉移以及負載平衡能力。當服務器失敗的時候,就無法獲取整個網站的內容,除非服務器被重新喚起。由于服務器失效,任何存儲在服務器內存中的SESSION都會丟失,用戶必須重新登陸并且輸入所有由于服務器失效丟失的數據。

              不同的是,作為集群一部分的服務器則提供了可測性以及失效無縫轉移能力。一個集群就是一組同步運行并且協同工作,能提供高可靠性,高穩定性以及高可測性的多服務器例程。服務端集群對客戶端表現出來似乎就是一個單獨的服務器例程。從客戶端的視角來看,集群的客戶端和單獨的服務器沒多大不同,但是他們通過提供實效無縫轉移和SESSION復制做到了不間斷服務以及SESSION數據持久性。

              集群中的服務器通訊

              集群中的應用程序服務器通過諸如IP多點傳送(IP multicast)和IP sockets這樣的技術和其他服務器共享信息

              ●IP多點傳送:主要用于1對多的服務器通訊,通過廣播服務和 heartbeats消息的可用來顯示服務器的有效

              ●IP sockets:主要用于在集群的服務器例程中進行P2P服務器通訊

              使用ip多點傳送進行一對多通訊

              TOMCAT服務器使用IP多點傳送在集群中的服務器例程間進行一對多的通訊,IP多點傳送是一種能夠讓多服務器向指定IP地址和端口號進行訂閱并且監聽消息的廣播技術(多點傳送IP地址范圍從224.0.0.0 到239.255.255.255)。在集群中的每個服務器都使用多點傳送廣播特定的 heartbeat消息,通過監視這些 heartbeat消息,在集群中的服務器例程判斷什么時候服務器例程失效。在服務器通訊中使用IP多點傳送的一個缺點是他不能保證這些消息被確實接收到了。例如,一個應用持續的本地多點傳送緩存滿了,就不能寫入新的多點傳送消息,等消息過了之后該應用程序就沒有被通知到。

              使用ip Sockets進行服務器通訊

              IP sockets 同樣也通過了一套在集群中的服務器間進行發送消息和數據的機制。服務器例程使用IP sockets 在集群節點間進行HTTP SESSION狀態的復制。正確的SOKET配制對于集群的性能是至關重要的,基于SOCKET的通訊的效率取決于SOCKET的實現類別(例如:系統使用本地的或者純JAVA SOCKET讀取器實現),如果服務器使用純JAVA SOCKET讀取器則要看服務器例程是否注冊使用了足夠的SOCKET讀取器線程。

              如果想要有最佳的SOCKET性能,系統應該注冊使用本地的SOCEKT而不是純JAVA實現。這是因為相對于基于JAVA的SOCKET實現,本地SOCKET消耗更少的系統資源。雖然SOCKET讀取器的JAVA實現是P2P通信中一種可靠而且可移動的方法,可是他不能為集群中的重型SOCKET使用提供最好的性能。當判斷從SOCKET是否有數據讀取的時候本地SOCKET讀取器使用了更有效率的方法。使用本地SOCKET讀取器實現,讀取器線程不需要去統計靜止的SOCKET:他們僅僅為活動的SOCKET服務,并且在一個給定的SOCKET開始活躍起來時他們可以立刻捕捉到。而使用純JAVA SOCKET讀取器,線程必須動態的統計所有打開的SOCKET,判斷他們是否包含可讀取的數據。換句話說,SOCKET讀取器總是忙于統計SOCKET,即使這些SOCKET沒有數據可讀。這些本不應該的系統開銷降低了性能。

              TOMCAT 5中的集群

              雖然在TOMCAT5的早些版本中也有集群的功能,但是在稍后的版本中(5.0.19或者更高),集群變的更加模塊組件化。在 server.XML 中集群元素已經被重構,這樣我們可以替換集群的不同部分而不會影響其他元素。例如,當前配置中把成員服務設置為多點傳送發現。這里可以輕易地把成員服務修改替換為使用TCP或者 Unicast ,而不會改變集類邏輯的其他部分。

              其他一些集群元素,例如SESSION管理器,復制發送端,復制接受端也可以被自定義的實現取代而不影響集群配置的其他部分。同樣,在TOMCAT集群中的任何服務器組件可以使用集類API向集群中的所有成員發送消息。

              SESSION復制

              服務器集群通常操縱兩種SESSION: sticky sessions和 replicated sessions .sticky sessions就是存在單機服務器中的接受網絡請求的SESSION,其他集群成員對該服務器的SESSION狀態完全不清楚,如果存有SESSION的服務器失敗的話,用戶必須再次登陸網站,重新輸入所有存儲在SESSION中的數據。

              另一種SESSION類型是,在一臺服務器中SESSION狀態被復制到集群中的其他所有服務器上,無論何時,只要SESSION 被改變,SESSION數據都要重新被復制。這就是 replicated session . sticky 和 replicated sessions都有他們的優缺點, Sticky sessions簡單而又容易操作,因為我們不必復制任何SESSION數據到其他服務器上。這樣就會減少系統消耗,提高性能。但是如果服務器失敗,所有存儲在該服務器內存中的SESSION數據也同樣會消失。如果SESSION數據沒有被復制到其他服務器,這些SESSION就完全丟失了。當我們在進行一個查詢事務當中的時候,丟失所有已經輸入的數據,就會導致很多問題。

              為了支持 jsp HTTP session 狀態的自動失效無縫轉移,TOMCAT服務器復制了在內存中的SESSION狀態。這是通過復制存儲在一臺服務器上的SESSION數據到集群中其他成員上防止數據丟失以及允許失效無縫轉移。

              對象的狀態管理

              通過在服務器上的保存狀態可以區分出4種對象:

              ●無狀態:一個無狀態對象在調用的時候不會在內存中保存任何狀態,因為客戶端和服務器端沒必要保存任何有關對方的信息。在這種情況下,客戶端會在每次請求服務器時都會發送數據給服務器。SESSION狀態被在客戶端和服務器端來回發送。這種方法不總是可行和理想的,特別是當傳輸的數據比較大或者一些安全信息我們不想保存在客戶端的時候;

              ●會話:一個會話對象在一個SESSION中只被用于特定的某個客戶端。在SESSION中,他可以為所有來自該客戶端的請求服務,并且僅僅是這個客戶端的請求。貫穿一個SESSION,兩個請求間的狀態信息必須保存。會話服務通常在內存中保存短暫的狀態,當在服務器失敗的時候可能會丟失。SESSION狀態通常被保存在請求間的服務器的內存中。為了清空內存,SESSION狀態也可以被從內存中釋放(就像在一個對象CACHE)。在該對象中,性能和可量測性都有待提高,因為更新并不是被單獨的寫到磁盤上,并且服務器失敗的時候數據也沒辦法搶救。

              ●緩存:緩存對象在內存中保存狀態,并且使用這個去處理從多客戶端來的請求。緩存服務的實現可以擴展到他們把緩存的是數據備份保存在后端存儲器中(通常是一個關系數據庫)。

              ●獨立的:一個獨立的對象在一個時間內只活躍在集群中的一臺服務器上,處理來自多客戶端的請求。他通常由那些私有的,持久的,在內存中緩寸的數據支持。他同樣也在內存中保持短暫狀態,在服務器失敗的時候要重建或者丟失。當失敗的時候,獨立對象必須在同一個服務器上重起或者移植到另一臺服務器上。

              SESSION復制的設計考慮事項

              網絡考慮事項

              把集群的多點傳送地址和其他應用程序隔離是至關重要的。我們不希望集群配置或者網絡布局干擾到多點傳送服務器通信。和其他應用程序共享集群多點傳送地址將迫使集群的服務器例程處理不應該的消息,消耗系統內存。共享多點傳送地址可能也會使IP多點傳送緩沖過載,延遲服務器 heartbeat 消息傳輸。這樣的延遲可能導致一個服務器例程被標識為死亡,僅僅因為他的 heartbeat 消息沒有被及時接收。

              編程考慮事項

              除了上面提到的網絡相關因素,還有些和我們寫 J2EE 網絡應用程序有關的設計考慮也會影響SESSION復制。以下列出了一些編程方面的考慮:

              ●SESSION數據必須被序列化:為了支持HTTP session 狀態的內存內復制,所有的 servlet 和 JSP session 數據必須被序列化,對象中的每個域都必須被序列化,這樣對象被可靠的序列化。

              ●把應用程序設計為冪等的:冪等的的意思就是一個操做不會修改狀態信息,并且每次操作的時候都返回同樣的結果(換句話說就是:做多次和做一次的效果是一樣的),通常,WEB請求,特別是 HTML forms 都被發送多次(當用戶點擊發送按紐兩次,重載頁面多次),導致多次HTTP請求。設計SERVLET和其他WEB對象為 冪等的,可以容忍多次請求。詳細可以去參考設計模式“Synchronized Token ”和“Idempotent Receiver ”關于怎樣設計冪等的的應用程序。

              ●在BUSINESS層存儲狀態:會話狀態應該使用有狀態的SESSION BEANS存儲在EJB層,而不是存儲在WEB層的HttpSession.因為企業應用程序要支持各種類型客戶端(WEB客戶端,JAVA應用程序,其他EJB),存儲數據在WEB層會導致在客戶端的雙數據存儲。因此,有狀態的SESSION BEAN在這些情況下就被用于存儲SESSION狀態。無狀態的SESSION BEAN要為每次的調用重構造會話狀態。這些狀態可能必須從數據庫中恢復的數據中重編譯。這些缺點失去了使用無狀態SESSION BEAN去提高性能和可測量性的目的,嚴重的減低了性能。

              ●序列化系統消耗:序列化SESSION數據在復制SESSION狀態的時候回會些系統消耗。隨著序列化對象大小的增長消耗也越多。最好是保持SESSION容量適當的小。但是如果你必須在SESSION中創建非常大的對象,最好測試下你的 servlets 性能以保證性能是可接受的以及SESSION的復制時間是適當的。

              ●用戶SESSION:判斷在集群中每個TOMCAT服務器例程所控制的并發用戶SESSION最大數是很重要的。為了控制更多并發SESSION,我們應該為提高效率添加更多內存。最大并發客戶端數,以及每個客戶端請求的頻率在決定SESSION復制對服務器的性能影響方面也是個因素。

              Tomcat 5中的SESSION復制

              在版本5之前,TOMCAT服務器只支持sticky sessions (使用mod_jk模塊進行負載平衡)。如果我們需要SESSION復制,必須依靠第3方軟件例如JavaGroups 去實現。

              Tomcat 5服務器帶有SESSION復制功能。和集群特征類似,只要修改 server.xml 注冊文件就能實現SESSION復制。

              Martin Fowler 在他的書《 Enterprise Patterns》中談到三個SESSION狀態持久性模式,這些模式包括:

              1.客戶端SESSION狀態:在客戶端存儲SESSION狀態

              2.服務端SESSION狀態:在一個序列化的FORM中保持SESSION狀態到一個服務器系統上。

              3.數據庫SESSION狀態:當在數據庫中提交數據的時候存儲SESSION數據。

              TOMCAT支持以下三種SESSION持久性類型:

              1.內存復制:在JVM內存中復制SESSION狀態,使用TOMCAT 5安裝帶的SimpleTcpCluster 和 SimpleTcpClusterManager 類。這些類在包org.apache.catalina.cluster中,是server/lib/catalina-cluster.jar的一部分。

              2.數據庫持久性:在這種類型中,SESSION狀態保存在一個關系數據庫中,服務器使用JDBCManager類從數據庫中獲取SESSION信息。這個類在包org.apache.catalina.session.JDBCStore中,是catalina.jar的一部分。

              3.基于文件的持久性:這里使用類PersistenceManager把SESSION狀態保存到一個文件系統。這個類在包org.apache.catalina.session.FileStore中,是catalina.jar的一部分。

              TOMCAT集群元素以及SESSION復制這章簡要介紹一下組成TOMCAT集群的元素以及SESSION復制。

              集群:

              這個是集群中的主要元素, SimpleTcpCluster類代表這個元素。他使用在server.xml中指定的管理類為所有可分配的web contexts生成ClusterManager.

              集群管理器

              這個類關注跨越集群中所有節點間的SESSION數據復制。所有在web.xml文件中指定了distributable標記的WEB應用程序都會有SESSION復制。集群管理器作為集群元素的managerClassName屬性在server.xml中指定。集群管理器的代碼主要被設計用來分離集群中的元素。我們所要做的就是寫一個SESSION管理器類去實現ClusterManager接口。這樣能讓我們靈活的使用客戶集群管理器而不會影響到集群中的其他元素。這里有兩個復制算法。SimpleTcpReplicationManager每次復制全部的SESSION,而DeltaManager只復制SESSION增量。

              最簡單的復制管理器在每次HTTP請求都復制所有的SESSION.在SESSION較小的時候這樣是很有用的,我們可以只用以下代碼:

              HashMap map = session.getAttribute("map");map.put("data","data");

              這里,我們不需要特別調用session.setAttribute() 或者 removeAttribute方法去復制SESSION變化。對于每次HTTP請求,在SESSION中的所有屬性都被復制。可以使用一個叫做useDirtyFlag的屬性去最優化SESSION被復制的次數。如果這個標記被設為真,我們必須調用setAttribute()方法去獲取被復制的SESSION變化。如果被設為假,每次請求后SESSION都被復制。

              SimpleTcpReplicationManager生成ReplicatedSession執行SESSION復制工作。

              提供增量管理器僅僅是為了性能的考慮。它在每次請求的時候都做一次復制。同時他也調用監聽器,所以如果我們調用session.setAttribute(),那么在其他服務器上的監聽器就會被調用。DeltaManager生成DeltaSession執行 SESSION復制。

              成員成員的建立通過由TOMCAT例程在同樣的多點傳送IP和端口發送廣播消息。廣播的消息包括服務器的IP地址和TCP監聽端口(默認IP地址為228.0.0.4)。

              如果在給定的時間框架內一個例程沒有接收到消息(由集群配置中的mcastDropTime參數指定),成員被認為死亡。這個元素由McastService類表示。

              由mcastXXX開始的屬性用于成員資格多點傳送PING.以下表格列出了用于IP多點傳送服務器通訊的屬性。

           

              發送端

              這個元素由ReplicationTransmitter類代表。當多點傳送廣播消息被接收到,成員被添加到機群。在下次復制請求前,發送例程將使用主機和端口信息建立一個TCP SOCKET.使用這些SOCKET發送序列化的數據。在TOMCAT 5中有3種不同的方法操縱SESSION復制:異步,同步,池復制模式。以下部分解釋了這些模式怎樣工作,以及他們將被使用在什么情況下。

              ●異步:在這種復制模式中,每個集群節點都有一個單線程扮演SESSION數據傳送器。這里,請求線程會把復制請求發送到一個隊列,然后返回給客戶端。在失效無縫轉移前,如果我們擁有sticky sessions,就應該使用異步復制。這里復制時間不是至關重要的,但是請求時間卻是。在異步復制期間,請求在數據被復制完之前就返回了。這種復制模式縮短了請求時間。當每個請求被分的更開這種模式是很有用的。(例如,在WEB請求間有更長的延遲)。同樣,如果我們不關心SESSION是否完成復制這個也很有用,當SESSION很較小時, SESSION復制時間也更短。

              ●同步:在這種模式中,一個單線程執行了HTTP請求和數據復制。集群中所有節點都接收到SESSION數據后線程才返回。同步意味被被復制的數據通過一個單SOCKET發送。由于使用一個單線程,同步模式可能會是簇性能的一個潛在的瓶頸。這種復制模式保證了在請求返回前SESSION        已經被復制。

              ●池:TOMCAT5 在使用池復制模式進行SESSION復制的方法上提供了很大的改進。

              池模式基本上是同步模式的一個擴展版本。他基于的一個原則是:劃分服務到多例程,每個例程處理不同的SESSION數據片段。同時對接收服務器開放多SOCKET進行發送SESSION信息。這種方法比通過單SOCKET發送所有東西更快。因此,使用一個SOCKET池同步復制SESSION.直到所有SESSION數據都復制完請求才返回。為了有效使用這種模式要增加TCP線程。由于使用多SOCKET,池模式使得集群性能的逐步提高以及更好的可測性。同樣這種模式也是最安全的配置,因為它有足夠數量的SOCKET在理想的時間內發送所有的SESSION數據到其他節點上。而使用單SOCKET,SESSION數據在通過集群時可能丟失或者只是部分傳輸。

              接收端

              這個集群元素由類ReplicationListener表示。在集群配置中以tcpXXX開始的屬性用于TCP SESSION復制。以下表格列出了用于配置服務器復制中基于SOCEKT服務器通訊的屬性。

              復制值

              復制值用于判斷哪些HTTP請求需要被復制。由于我們不經常復制靜態內容(例如HTML和javascript, stylesheets,圖像文件),我們可以使用復制值元素過濾掉靜態內容。這個值可以用于找出什么時候請求已完成以及初始化復制。

              部署器

              部署器元素可以用于部署集群范圍的應用程序。通常,部署只部署/解除部署簇內的工作成員。所以在損壞的節點在啟動時沒有WARS的復制。當watchEnabled="true"時配置器為WAR文件監視一個目錄(watchDir)。當添加一個新的WAR文件時,WAR被部署到本地例程,然后被部署到集群中的其他例程。當一個WAR文件從watchDir刪除,這個WAR被從本地和集群范圍內解除部署。

              所有在TOMCAT集群結構中的元素以及他們的層次關系都在列在圖1中

           

              圖 1. Tomcat 集群等級結構圖。單擊看原圖。

              TOMCAT中SESSION復制是怎么工作的

              以下部分簡要解釋當TOMCAT服務器啟動或則關閉時集群節點怎樣分享SESSION信息,詳細信息可參考Tomcat 5 Clustering文擋。

              TC-01:集群中第一個節點TC-02:集群中第2個節點

              ●服務器啟動:TC-01使用標準服務器啟動隊列啟動。當主機對象被創建,即有一個集群對象和它相關聯。當contexts被解析,如果distributable已經在web.xml中指定,那么TOMCAT為WEB CONTEXT創建SESSION管理器(SimpleTcpReplicationManager 取代StandardManager)。集群將會啟動一個成員服務(成員的一個例程)和一個復制服務。

              當TC-02啟動,他也遵循第一個成員(TC-01)同樣的隊列但是有一個不同。集群被啟動并且創建一個成員關系(TC-01,TC-02)。TC-02將向TC-01請求SESSION狀態。TC-01回應該請求,在TC-2開始監聽HTTP請求前,TC-01發送狀態給TC-02.如果TC-01不回應,TC-02將在60秒后進入中止狀態并且發布一個日志入口。SESSIONG 狀態發送給所有在web.xml中指定了distributable的WEB應用程序。

              ●創建SESSION:當TC-01接收到請求,一個SESSION(S1)被創建,處理進入TC-01的請求和沒有SESSION復制時是一樣的。當請求完成時會有以下事件:ReplicationValve將會在回應返回給用戶前截取請求。這里,會發現SESSION已經被改變,使用TCP復制SESSION到TC-02.●服務器儲運損耗/關閉:當在集群中的一臺服務器失敗,維護損壞或者系統升級,其他節點會受到第一個節點已經脫離集群的通知。TC-02從他的成員資格列刪除TC-01,并且TC-02在也不會收到有關TC-01任何變動的通知。負載平衡將會移至TC-02,所有的SESSION由TC-02控制。

              當TC-01開始恢復,他再次遵循在服務器開始階段描述的啟動隊列。加入到簇中并且以所有SESSIONG的當前狀態和TC-02通訊。一旦接收到        SESSIONG狀態,也就完成了加載然后打開它的HTTP/ mod_jk端口。所以,要等到從TC-2接受到SESSION狀態TC-01才能發送請求。

              ●SESSION終止:如果在第一個節點的一個SESSION已經無效或則由于過期終止,無效請求將被截取,SESSION會被同其他無效SESSION放在一個隊列中。當請求完成,服務器發送SESSIONG終止消息給TC-02而不是發送已經改變的SESSION,TC-02同樣也會把該SESSION置無效。我們可以從服務器控制臺看到SESSIONG無效的消息。無效SESSION在集群中將不會被復制,直到其他請求傳出系統并且檢查無效隊列。

          posted @ 2009-12-05 17:45 睫晉姬 閱讀(313) | 評論 (0)編輯 收藏

          選擇JSF不選Struts的十大理由

                  我的一個客戶不知道該選用Struts還是JSF。就像你預料的那樣,我通常會問:這2中框架之間有什么區別?當然,除了我的這個客戶外很多人都面臨這樣的選擇。

                  總的來說,我建議在新項目中優先考慮JSF。雖然常常有一些商業上的因素迫使我們為現有的項目選擇了Struts,而且那些解決方案還有待考驗,但是,讓我們面對一個事實:JSF比Struts好多了。


                  下面是我選擇JSF而不選Struts的十大理由:

                  1.Components(組件)

                  2.Render Kits

                  3.Renderers

                  4.Value Binding Expressions(值綁定表達式)

                  5.Event Model(事件模型)

                  6.Extensibility(可擴展性)

                  7.Managed Beans(Dependency Injection 依賴注入)

                  8.POJO Action Methods

                  9.JSF is the standard Java-based web app framework (JSF是java web應用程序的標準框架)

                  10.There's only one Struts(只有一個Struts)


                  10.There's only one Struts(只有一個Struts)

                  Struts是一個開源產品,然而JSF是一個標準。這個細節常常被新的JSF學習者忽略,其實這是顯而易見的,因為我們有多個JSF的實現。雖然JSF還很不成熟,但是我們已經有了2個優秀的JSF實現可以選擇:Sun的參考實現和Apache的MyFaces。另一方面,我們只有一個Struts。


                  9.JSF is the standard(JSF是標準)

                  JEE 5.0要提供一個JSF的實現,這表明JSF不久將會無處不在。這可能與你無關,但是和工具供應商密切相關。現在大概有50個java web應用程序框架,工具供應商不會情愿去支持一個特別的框架,但是他們會毫不猶豫的去支持一個衛星電視器材標準。而且不止供應商,開源項目也會迅速的聚集在JSF的四周,爭先恐后的去實現相同的功能。比如說,直到我們去實現本質上和Shale的Tapestry差不多的視圖的時候,我才知道Facalets。(從長遠來看,我相信這種冗余是件好事,會給我們帶來好處)


                  8.POJO Action Methods

                  Struts的行為是和Struts的API綁定在一起的,但是JSF的行為方法可以在POJPO中實現。這意味著你不用在表單和模型對象之間實現一個多余的行為層。順便說一下,在JSF里面沒有行為對象,行為在模型對象中實現。但是也請注意一點:如果你愿意你也可以生成與JSF獨立的行為對象。在Struts里面,你有Form. Bean和Action Bean。Form. Bean包含數據而Action Bean包含邏輯。OO狂會想去合并前2者,在Struts你辦不到。但是在JSF中,你可以分開數據和邏輯,也可以合并到一個對象中,一切由你決定。


                  7.Managed Beans(Dependency Injection 依賴注入)

                  和Spring一樣,JSF也使用了依賴注入(DJ)(或控制反轉(IoC))去實例化和初始化Bean。Struts的確為你生成了Form. Bean和Action Bean,但是JSF可以為你生成各種各樣的Managed Bean。

                  6.Extensibility(可擴展性)

                  這個很重要。JSF有6個對象實現了這個框架的大部分功能,而且你可以很容易的用你自己的實現代替原有實現。比如你想加一個自定義參數在JSF表達式語言里面,或是添加一個自己的視圖控制器以便于區分組件和HTML。事實上Shale實現了上面的功能。如果你還沒有滿足,JSF提供了幾個地方你可以輕松的控制JSF的生命周期。Shale給你的會更多。


                  5.Event Model(事件模型)

                  JSF的事件模型使你可以對值改變,動作,JSF生命周期階段變換等作出反應。在JSF1.1中,那些事件都是在服務器端處理的,這肯定是一個缺陷,好在JSF2.0計劃支持客戶端事件,拭目以待吧。


                  4.Value Binding Expressions(值綁定表達式)

                  在Struts中,你負責把數據從Form傳遞到模型對象。你實現的Action的execute方法是把Form作為一個參數。然后你再手動的把數據從Form. Bean里面取出放到模型對象里面。你要為應用里面的每個Form做這些事情,然而在JSF里面,你只需像這樣:#{model.property} 就夠了,其他的交給JSF來處理。


                  3.Renderers

                  你有看過Struts的標簽的源代碼嗎?它直接生成HTML。JSF組件標簽什么都不生成,它和服務器上的一對component-renderer對應。Component維護組件狀態,rendered負責獲得視圖。重點是renderers是可插拔的,即你可以根據自己需求實現然后干洗加盟替代掉默認實現。比如說我在NFJS上面的Felix談話中舉例說明了怎么去實現一個自定義的label renderer。你只需要配置你的renderer,JSF就會自動在你的應用程序里面使用他。


                  2.Render Kits

                  在幾年前我曾經有份Struts咨詢工作,我們必須同時支持瀏覽器和無線設備,非常痛苦。但是用JSF來完成那個任務非常容易,因為你可以生成你自己的render kit-為一種特定顯示技術的renderers的集合-然后配置到JSF里面。


                  1.Components(組件)

                  組件是Struts和JSF之間最大的區別。就像Swing一樣,JSF提供豐富的底層構件去開發組件然后添加到標準的組件集。那些底層構件讓你很容易的生成自己的組件并且和別人共享?,F在我們到處都能看到自定義組件跳出來,比如說Oracle的ADF和MyFaces,兩者都提供了豐富的組件集,就像javascript日歷,tree等等。當然,組件只是一部分。典型的是,組件都和一個獨立的renderer對應,這給我們帶來了真正的好處(看第3條)。但是和JSF中的很多東西一樣,你不一定要墨守成規。只要你愿意,你可以實現render自己的組件,雖然這樣你會失去給組件加入別的renderer的能力。

          posted @ 2009-12-05 17:43 睫晉姬 閱讀(144) | 評論 (0)編輯 收藏

          Java理論與實踐: 描繪線程安全性

            定義線程安全性
            明確定義線程安全性出人意料地困難,大多數定義看上去完全是自我循環。快速搜索一下 Google,可以找到以下關于線程安全代碼的典型的、但是沒有多大幫助的定義(或者可以說是描述):

            ...可以從多個編程線程中調用,無需線程之間不必要的交互。

            ...可以同時被多個線程調用,不需要調用一方有任何操作。

            有這樣的定義,就不奇怪我們對于線程安全性會感到如此迷惑。這些定義比說“一個類在可以被多個線程安全調用時就是線程安全的”好不了多少,當然,它的意義就是如此,但是它不能幫助我們區分一個線程安全的類與一個線程不安全的類。安全的意義是什么呢?

            實際上,所有線程安全的定義都有某種程序的循環,因為它必須符合類的規格說明 -- 這是對類的功能、其副作用、哪些狀態是有效和無效的、不可變量、前置條件、后置條件等等的一種非正式的松散描述(由規格說明給出的對象狀態約束只應用于外部可見的狀態,即那些可以通過調用其公共方法和訪問其公共字段看到的狀態,而不應用于其私有字段中表示的內部狀態)。

            線程安全性

            類要成為線程安全的,首先必須在單線程環境中有正確的行為。如果一個類實現正確(這是說它符合規格說明的另一種方式),那么沒有一種對這個類的對象的操作序列(讀或者寫公共字段以及調用公共方法)可以讓對象處于無效狀態,觀察到對象處于無效狀態、或者違反類的任何不可變量、前置條件或者后置條件的情況。

            此外,一個類要成為線程安全的,在被多個線程訪問時,不管運行時環境執行這些線程有什么樣的時序安排或者交錯,它必須仍然有如上所述的正確行為,并且在調用的代碼中沒有任何額外的同步。其效果就是,在所有線程看來,對于線程安全對象的操作是以固定的、全局一致的順序發生的。

            正確性與線程安全性之間的關系非常類似于在描述 ACID(原子性、一致性、獨立性和持久性)事務時使用的一致性與獨立性之間的關系:從特定線程的角度看,由不同線程所執行的對象操作是先后(雖然順序不定)而不是并行執行的。

            方法之問的狀態依賴

            考慮下面的代碼片段,它迭代一個 Vector 中的元素。盡管 Vector 的所有方法都是同步的,但是在多線程的環境中不做額外的同步就使用這段代碼仍然是不安全的,因為如果另一個線程恰好在錯誤的時間里刪除了一個元素,則 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

            這里發生的事情是: get(index) 的規格說明里有一條前置條件要求 index 必須是非負的并且小于 size() 。但是,在多線程環境中,沒有辦法可以知道上一次查到的 size() 值是否仍然有效,因而不能確定 i

            更明確地說,這一問題是由 get() 的前置條件是以 size() 的結果來定義的這一事實所帶來的。只要看到這種必須使用一種方法的結果作為另一種講法的輸入條件的樣式,它就是一個 狀態依賴,就必須保證干洗設備至少在調用這兩種方法期間元素的狀態沒有改變。一般來說,做到這一點的唯一方法在調用第一個方法之前是獨占性地鎖定對象,一直到調用了后一種方法以后。在上面的迭代 Vector 元素的例子中,您需要在迭代過程中同步 Vector 對象。

            線程安全程度

            如上面的例子所示,線程安全性不是一個非真即假的命題。 Vector 的方法都是同步的,并且 Vector 明確地設計為在多線程環境中工作。但是它的線程安全性是有限制的,即在某些方法之間有狀態依賴(類似地,如果在迭代過程中 Vector 被其他線程修改,那么由 Vector.iterator() 返回的 iterator 會拋出 ConcurrentModificationException )。

            對于 Java 類中常見的線程安全性級別,沒有一種分類系統可被廣泛接受,不過重要的是在編寫類時盡量記錄下它們的線程安全行為。

            Bloch 給出了描述五類線程安全性的分類方法:不可變、線程安全、有條件線程安全、線程兼容和線程對立。只要明確地記錄下線程安全特性,那么您是否使用這種系統都沒關系。這種系統有其局限性 -- 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 -- 但是這套系統是一個很好的起點。這種分類系統的核心是調用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節分別描述了線程安全性的這五種類別。

            不可變

            本欄目的普通讀者聽到我贊美不可變性的優點時不會感到意外。不可變的對象一定是線程安全的,并且永遠也不需要額外的同步。因為一個不可變的對象只要構建正確,其外部可見狀態永遠也不會改變,永遠也不會看到它處于不一致的狀態。Java 類庫中大多數基本數值類如 Integer 、 String 和 BigInteger 都是不可變的。

            線程安全

            線程安全的對象具有在上面“線程安全”一節中描述的屬性 -- 由類的規格說明所規定的約束在對象被多個線程訪問時仍然有效,不管運行時環境如何排列,線程都不需要任何額外的同步。這種線程安全性保證是很嚴格的 -- 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴格的定義。

            有條件的線程安全

            我們在 7 月份的文件“ 并發集合類”中討論了有條件的線程安全。有條件的線程安全類對于單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器 -- 由這些類返回的 fail-fast 迭代器假定在迭代器進行遍歷的時候底層集合不會有變化。為了保證其他線程不會在遍歷的時候改變集合,進行迭代的線程應該確保它是獨占性地訪問集合以實現遍歷的完整性。通常,獨占性的訪問是由對鎖的干洗同步保證的 -- 并且類的文檔應該說明是哪個鎖(通常是對象的內部監視器(intrinsic monitor))。

            如果對一個有條件線程安全類進行記錄,那么您應該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的并發訪問。用戶可以合理地假設其他操作序列不需要任何額外的同步。

            線程兼容

            線程兼容類不是線程安全的,但是可以通過正確使用同步而在并發環境中安全地使用。這可能意味著用一個 synchronized 塊包圍每一個方法調用,或者創建一個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。也可能意味著用 synchronized 塊包圍某些操作序列。為了最大程度地利用線程兼容類,如果所有調用都使用同一個塊,那么就不應該要求調用者對該塊同步。這樣做會使線程兼容的對象作為變量實例包含在其他線程安全的對象中,從而可以利用其所有者對象的同步。

            許多常見的類是線程兼容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。

            線程對立

            線程對立類是那些不管是否調用了外部同步都不能在并發使用時安全地呈現的類。線程對立很少見,當類修改靜態數據,而靜態數據會影響在其他線程中執行的其他類的行為,這時通常會出現線程對立。線程對立類的一個例子是調用 System.setOut() 的類。

            其他線程安全記錄考慮

            線程安全類(以及線程安全性程度更低的的類) 可以允許或者不允許調用者鎖定對象以進行獨占性訪問。 Hashtable 類對所有的同步使用對象的內部監視器,但是 ConcurrentHashMap 類不是這樣,事實上沒有辦法鎖定一個 ConcurrentHashMap 對象以進行獨占性訪問。除了記錄線程安全程序,還應該記錄是否某些鎖 -- 如對象的內部鎖 -- 對類的行為有特殊的意義。

            通過將類記錄為線程安全的(假設它確實 是線程安全的),您就提供了兩種有價值的服務:您告知類的維護者不要進行會影響其線程安全性的修改或者擴展,您還告知類的用戶使用它時可以不使用外部同步。通過將類記錄為線程兼容或者有條件線程安全的,您就告知了用戶這個類可以通過正確使用同步而安全地在多線程中使用。通過將類記錄為線程對立的,您就告知用戶即使使用了外部同步,他們也不能在多線程中安全地使用這個類。不管是哪種情況,您都在潛在的嚴重問題出現 之前防止了它們,而要查找和修復這些問題是很昂貴的。

            結束語

            一個類的線程安全行為是其規格說明中的固有部分,應該成為其文檔的一部分。因為(還)沒有描述類的線程安全行為的聲明式方式,所以必須用文字描述。雖然 Bloch 的描述類的線程安全程度的五層系統沒有涵蓋所有可能的情況,但是它是一個很好的起點。如果每一個類都將這種線程行為的程度加入到其 Javadoc 中,那么可以肯定的是我們大家都會受益。

          posted @ 2009-12-05 17:40 睫晉姬 閱讀(130) | 評論 (0)編輯 收藏

          J2ME中實現多線程技術總結

              我們知道,在操作系統級別上軟件的運行一般都是以進程為單位,而在每個進程的運行過程中允許同時并發執行多個不同線程,這就使得一個程序能同時執行不同的操作。使用多線程的目的是為了最大限度地利用計算機CPU資源。JAVA程序字節碼最終是在JVM虛擬機下運行的,同一虛擬機進程中的不同操作都是通過多線程來運行的。在JAVA虛擬機中,線程常用有單線程和多線程,單線程指程序執行過程只是一個有效操作的序列,不同操作都有著明確的先后順序;而多線程允許同時進行著不同的操作,這些不同的操作同時并發進行著,并由CPU時鐘頻率根據不同的調度方式對他們進行執行調度。

              在JAVA語言中提供了豐富的多線程操縱接口,提供了各類不同的線程實現方法供我們選擇,功能非常強大。在手機軟件設計中,由于同樣需要執行網絡連接(基于HTTP的高級Internet協議通訊)、UI調度等待、UI顯示幻化、游戲控制等操作需要通過后臺的上海保潔數據運算或UI不斷更新等操作。因此在J2ME中,KVM虛擬機也提供了功能強大的多線程API,使我們同樣能在J2ME中實現線程的并發運算。

              在J2ME中,主要有以下三種方法實現多線程。

              一、繼承Thread類(java.lang.Thread)

              通過編寫線程類繼承Thread類并重寫Thread類中的run()方法實現線程,當線程對象被運行時候將會自動執行run方法中的實體內容,從而開辟一個單獨的線程并運行起來。

                   如:public class ThreadSimple extends Thread{
                            public ThreadSimple()
                            {
                                  //constructor
                             }
                            public void run()
                            {
                                  //run code entity
                            }
                   }


              線程實例使用,直接創建對象并調用start()方法即可運行線程。

              new ThreadSimple()。start();當執行start方法時候,將會自動運行run方法,但是執行start方法時候只做了一件事,就是將線程轉化為可執行狀態,然后等待操作系統進行調度并運行,因此無法保證線程能立即啟動。在JAVA中,Thread類實現了Runnable接口,因此run方法是通過實現接口Runnable中的抽象方法。

              二、直接實現Runnable多線程接口(java.lang.Runnable)

              線程接口Runnable中只有一個抽象方法run,通過實現Runnable接口中的方法的類即可創建出有多線程特征的對象,但該對象并無法使其啟動線程,需要作為參數并借助Thread的構造方法構造創建對象并調用start方法對線程進行啟動。

                   如:public class RunnablSimple implements Runnable{
                            public RunnableSimple()
                            {
                                   //constructor
                            }
                            public void run(){
                                  //run code entity
                         }
                }


              實現類型的對象使用:

              RunnableSimple rs = new RunnableSimple();

              new Thread(rs).start();

              由此可見,以上兩種方法都是通過Thread的start來啟動線程的,實際上所有的線程操作都是封裝在Thread這個類中,由Thread對象調用各種接口來控制線程。

              J2ME中線程中主要方法:void setPriority(int newPriority),設置線程優先級,在操作系統中線程的調度是不確定性的,可以通過該方法設置相應線程的優先級別。

              static void sleep(long millis) ,線程中靜態方法,用于讓線程進入休眠狀態,執行該方法將會讓線程在指定時間millis毫秒內休眠。

              void start(),使現在進入可執行狀態。

              void run() ,線程執行主體。

              void join(),等待該線程終止。

              boolean isAlive(),用于判斷線程是否出于Alive狀態。

              static void yield() ,盡量讓其他線程先執行。
              三、使用任務組合實現多線程

              在J2ME中,同樣具有JAVA中的任務處理組合類,他們分別為Timer和TimerTask,可以使用他們實現多線程,簡單說就是定時實現任務。

              Timer是JAVA中的一個定時器,可以實現在某一時間做某件事或者在某一時間段做某些事,分別通過方法schedule(TimerTask tt,long millis)和schedule(TimerTask tt,long start,long off)。

              TimerTask是一個任務類,通過繼承該類并覆蓋方法run即可創建一個任務。

                如:public class TimerTaskS extends TimerTask{
                         public TimerTaskS(){
                               //constructor
                         }
                         public void run(){
                              //run code entity
                   }
                }


              任務調用:

              Timer timer = new Timer();

              //3秒鐘后執行任務

              timer.schedule(new TimerTaskS(),3000);

              //3秒鐘后執行任務并且之后每5秒鐘執行一次

              timer.schedule(new TimerTaskS(),3000,5000);

              有此可見在使用計時任務可以達到實現線程的效果,分別執行不同的并發操作,通過Timer類對象來操作TimerTask對象,通過schedule方法來計時執行任務,在結束任務的時候,通常使用cancel()來實現。

              通常情況下,在J2ME軟件中我們通過手機按鍵來觸發一系列相應的操作,在程序響應處理過程中較多會涉及網絡操作、數據存儲等相對消耗時間和資源的操作,而這些操作往往需要一定的時間才能完成,因此在處理按鍵響應過程中通常我們需要建立干洗設備線程處理,避免程序出現死機現象。

                public void commandAction(Command c, Displayable s) {
           if(c==do1Com){
            //創建實現接口線程
            new Thread(new RunnableSimple()).start();
           }
           else if(c==do2Com){
            //創建繼承Thread線程
                  new ThreadSimple().start();
           }
               else{
                 //創建任務線程
                 new Timer().schedule(new TimerTaskS(),3000,20);
             }
          }

          posted @ 2009-12-05 17:37 睫晉姬 閱讀(196) | 評論 (0)編輯 收藏

          Java設計模式之計數代理模式

            描述:

            計數代理模式在客戶對象調用服務提供者對象上方法的前后執行諸如日志(logging)和計數(counting)一系列附加功能時很有用。計數代理模式建議把這些附加功能封裝在一個單獨的對象,這個對象就是指計數代理對象,而不是把這些附加的功能實現放到服務提供者的內部。良好的對象設計的一個特征就是對象要專注于提供特定的功能。換句話說,理想的對象不應該做各種不相干高周波的事情。把諸如日志(logging)和計數(counting)等類似的功能封裝為一個單獨的對象,而讓服務提供者對象僅提供它自己的特定功能。也就是說,只允許服務提供者對象執行定義良好、特定的任務。

            計數代理被設計成可以被客戶訪問的與服務提供者具有相同接口的對象。客戶對象不是直接訪問服務提供者,而是調用計數代理對象上的方法,計數代理執行必要的紀錄日志(logging)和計數(counting)功能后,再把方法調用傳遞給服務提供著對象。如圖1

            Figure1: Generic Class Association When the Counting Proxy Pattern Is Applied

            下面的例子說明了如何在應用程序中利用計數代理。

            例子:

            讓我們設計一個Order類,類層次如圖2,OrderIF接口聲明了getAllOrders讀取數據庫中所有訂單的簡單方法。

            Figure2: Order Class Hierarchy

          public interface OrderIF {

            public Vector getAllOrders();

            }

            作為getAllOrders方法實現的一部分,Order類實用了FileUtil工具類從order.txt文件中讀取訂單項。

          public class Order implements OrderIF {

            public Vector getAllOrders() {

            FileUtil fileUtil = new FileUtil();

            Vector v = fileUtil.fileToVector("orders.txt");

            return v;

            }

            }


            讓我們假定在調用getAllOrders()時,需要把取數據文件所花費的時間和記錄條數要吸親自出吸塑機記錄的log日志文件中。

            這個附加的功能可以設計一個單獨的OrderProxy類來實現,它與真實對象Order一樣實現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就想調用真實對象Order一樣調用OrderProxy對象上的getAllOrders()方法,OrderProxy對象傳遞這個調用給真實對象Order,計算讀取所有訂單所花費的時間并使用FileUtil幫助類將其紀錄的log日志文件中。在這個過程中,OrderProxy扮演者計數代理的角色。

          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();

            }

            }
           

          posted @ 2009-12-05 17:35 睫晉姬 閱讀(136) | 評論 (0)編輯 收藏

          實戰體會Java多線程編程精要

           

            在 Java 程序中使用多線程要比在 C 或 C++ 中容易得多,這是因為 Java 編程語言提供了語言級的支持。本文通過簡單的編程示例來說明 Java 程序中的多線程是多么直觀。讀完本文以后,用戶應該能夠編寫簡單的多線程程序。

            為什么會排隊等待?

            下面的這個簡單的 Java 程序完成四項不相關的任務。這樣的程序有單個控制線程,控制在這四個任務之間線性地移動。此外,因為所需的資源 ? 打印機、磁盤、數據庫和顯示屏 -- 由于硬件和軟件的限制都有內在的潛伏時間,所以每項任務都包含明顯的等待時間。因此,程序在訪問數據庫之前必須等待打印機完成打印文件的任務,等等。如果您正在等待程序的完成,則這是對計算資源和您的時間的一種拙劣使用。改進此程序的一種方法是使它成為多線程的。

            四項不相關的任務


               class myclass {
            static public void main(String args[]) {
            print_a_file();
            manipulate_another_file();
            access_database();
            draw_picture_on_screen();
            }
            }


            在本例中,每項任務在開始之前必須等待前一項任務完成,即使所涉及的任務毫不相關也是這樣。但是,在現實生活中,我們經常使用多線程模型。我們在處理某些任務的同時也可以讓孩子、配偶和父母完成別的任務。例如,我在寫信的同時可能打發我的兒子去郵局買郵票。用軟件術語來說,這稱為多個控制(或執行)線程。

            可以用兩種不同的方法來獲得多個控制線程:

            多個進程

            在大多數操作系統中都可以創建多個進程。當一個程序啟動時,它可以為即將開始的每項任務創建一個進程,并允許它們同時運行。當一個程序因等待網絡訪問或用戶輸入而被阻塞時,另一個程序還可以運行,這樣就增加了干洗設備資源利用率。但是,按照這種方式創建每個進程要付出一定的代價:設置一個進程要占用相當一部分處理器時間和內存資源。而且,大多數操作系統不允許進程訪問其他進程的內存空間。因此,進程間的通信很不方便,并且也不會將它自己提供給容易的編程模型。

            線程

            線程也稱為輕型進程 (LWP)。因為線程只能在單個進程的作用域內活動,所以創建線程比創建進程要廉價得多。這樣,因為線程允許協作和數據交換,并且在計算資源方面非常廉價,所以線程比進程更可取。線程需要操作系統的支持,因此不是所有的機器都提供線程。Java 編程語言,作為相當新的一種語言,已將線程支持與語言本身合為一體,這樣就對線程提供了強健的支持。

            使用 Java 編程語言實現線程

            Java編程語言使多線程如此簡單有效,以致于某些程序員說它實際上是自然的。盡管在 Java 中使用線程比在其他語言中要容易得多,仍然有一些概念需要掌握。要記住的一件重要的事情是 main() 函數也是一個線程,并可用來做有用的工作。程序員只有在需要多個線程時才需要創建新的線程。

            Thread 類

            Thread 類是一個具體的類,即不是抽象類,該類封裝了線程的行為。要創建一個線程,程序員必須創建一個從 Thread 類導出的新類。程序員必須覆蓋 Thread 的 run() 函數來完成有用的工作。用戶并不直接調用此函數;而是必須調用 Thread 的 start() 函數,該函數再調用 run()。下面的代碼說明了它的用法:

            創建兩個新線程


               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 秒)在屏幕上顯示當前時間。這是通過創建兩個新線程來完成的,包括 main() 共三個線程。但是,因為有時要作為線程運行的類可能已經是某個類層次的一部分,所以就不能再按這種機制創建干洗機線程。雖然在同一個類中可以實現任意數量的接口,但 Java 編程語言只允許一個類有一個父類。同時,某些程序員避免從 Thread 類導出,因為它強加了類層次。對于這種情況,就要 runnable 接口。

            Runnable 接口

            此接口只有一個函數,run(),此函數必須由實現了此接口的類實現。但是,就運行這個類而論,其語義與前一個示例稍有不同。我們可以用 runnable 接口改寫前一個示例。(不同的部分用黑體表示。)

            創建兩個新線程而不強加類層次


              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();
            }
            }


            請注意,當使用 runnable 接口時,您不能直接創建所需類的對象并運行它;必須從 Thread 類的一個實例內部運行它。許多程序員更喜歡 runnable 接口,因為從 Thread 類繼承會強加類層次。

            synchronized 關鍵字

            到目前為止,我們看到的示例都只是以非常簡單的方式來利用線程。只有最小的數據流,而且不會出現兩個線程訪問同一個對象的情況。但是,在大多數有用的程序中,線程之間通常有信息流。試考慮一個金融應用程序,它有一個 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;
            }
            }


            在此代碼樣例中潛伏著一個錯誤。如果此類用于單線程應用程序,不會有任何問題。但是,在多線程應用程序的情況中,不同的線程就有可能同時訪問同一個 Account 對象,比如說一個聯合帳戶的所有者在不同的 ATM 上同時進行訪問。在這種情況下,存入和支出就可能以這樣的方式發生:一個事務被另一個事務覆蓋。這種情況將是災難性的。但是,Java 編程語言提供了一種簡單的機制來防止發生這種覆蓋。每個對象在運行時都有一個關聯的鎖。這個鎖可通過為方法添加關鍵字 synchronized 來獲得。這樣,修訂過的 Account 對象(如下所示)將不會遭受像數據損壞這樣的錯誤:

            對一個銀行中的多項活動進行同步處理


          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() 函數都需要這個鎖來進行操作,所以當一個函數運行時,另一個函數就被阻塞。請注意, checkBalance() 未作更改,它嚴格是一個讀函數。因為 checkBalance() 未作同步處理,所以任何其他方法都不會阻塞它,它也不會阻塞任何其他方法,不管那些方法是否進行了同步處理。

               Java 編程語言中的高級多線程支持

            線程組

            線程是被個別創建的,但可以將它們歸類到線程組中,以便于調試和監視。只能在創建線程的同時將它與一個線程組相關聯。在使用大量線程的程序中,使用線程組組織線程可能很有幫助。可以將它們看作是計算機上的目錄和文件結構。

            線程間發信

            當線程在繼續執行前需要等待一個條件時,僅有 synchronized 關鍵字是不夠的。雖然 synchronized 關鍵字阻止并發更新一個對象,但它沒有實現線程間發信。Object 類為此提供了三個函數:wait()、notify() 和 notifyAll()。以全球氣候預測程序為例。這些程序通過將地球分為許多單元,在每個循環中,每個單元的計算都是隔離進行的,直到這些值趨于穩定,然后相鄰單元之間就會交換一些數據。所以,從本質上講,在每個循環中各個線程都必須等待所有線程完成各自的任務以后才能進入下一個循環。這個模型稱為 屏蔽同步,下例說明了這個模型:

            屏蔽同步


           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();
            }
            }
            }


            當對一個線程調用 wait() 時,該線程就被有效阻塞,只到另一個線程對同一個對象調用 notify() 或 notifyAll() 為止。因此,在前一個示例中,不同的線程在完成它們的工作以后將調用 waitForAll() 函數,最后一個線程將觸發 notifyAll() 函數,該函數將釋放所有的線程。第三個函數 notify() 只通知一個正在等待的線程,當對每次只能由一個線程使用的資源進行訪問限制時,這個函數很有用。但是,不可能預知哪個線程會獲得這個通知,因為這取決于 Java 虛擬機 (JVM) 調度算法。

            將 CPU 讓給另一個線程

            當線程放棄某個稀有的資源(如數據庫連接或網絡端口)時,它可能調用 yield() 函數臨時降低自己的優先級,以便某個其他線程能夠運行。

            守護線程

            有兩類線程:用戶線程和守護線程。用戶線程是那些完成有用工作的線程。 守護線程是那些僅提供輔助功能的線程。Thread 類提供了 setDaemon() 函數。Java 程序將運行到所有用戶線程終止,然后它將破壞所有的守護線程。在 Java 虛擬機 (JVM) 中,即使在 main 結束以后,如果另一個用戶線程仍在運行,則程序仍然可以繼續運行。

            避免不提倡使用的方法

            不提倡使用的方法是為支持向后兼容性而保留的那些方法,它們在以后的版本中可能出現,也可能不出現。Java 多線程支持在版本 1.1 和版本 1.2 中做了重大修訂,stop()、suspend() 和 resume() 函數已不提倡使用。這些函數在 JVM 中可能引入微妙的錯誤。雖然函數名可能聽起來很誘人,但請抵制誘惑不要使用它們。

            調試線程化的程序

            在線程化的程序中,可能發生的某些常見而討厭的情況是死鎖、活鎖、內存損壞和資源耗盡。

            死鎖

            死鎖可能是多線程程序最常見的問題。當一個線程需要一個資源而另一個線程持有該資源的鎖時,就會發生死鎖。這種情況通常很難檢測。但是,解決方案卻相當好:在所有的線程中按相同的次序獲取所有資源鎖。例如,如果有四個資源 ?A、B、C 和 D ? 并且一個線程可能要獲取四個資源中任何一個資源的鎖,則請確保在獲取對 B 的鎖之前首先獲取對 A 的鎖,依此類推。如果“線程 1”希望獲取對 B 和 C 的鎖,而“線程 2”獲取了 A、C 和 D 的鎖,則這一技術可能導致阻塞,但它永遠不會在這四個鎖上造成死鎖。

            活鎖

            當一個線程忙于接受新任務以致它永遠沒有機會完成任何任務時,就會發生活鎖。這個線程最終將超出緩沖區并導致程序崩潰。試想一個秘書需要錄入一封信,但她一直在忙于接電話,所以這封信永遠不會被錄入。

            內存損壞

            如果明智地使用 synchronized 關鍵字,則完全可以避免內存錯誤這種氣死人的問題。

            資源耗盡

            某些系統資源是有限的,如文件描述符。多線程程序可能耗盡資源,因為每個線程都可能希望有一個這樣的資源。如果線程數相當大,或者某個資源的侯選線程數遠遠超過了可用的資源數,則最好使用 資源池。一個最好的示例是數據庫連接池。只要線程需要使用一個數據庫連接,它就從池中取出一個,使用以后再將它返回池中。資源池也稱為 資源庫。

            調試大量的線程

            有時一個程序因為有大量的線程在運行而極難調試。在這種情況下,下面的這個類可能會派上用場:


               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());
            }
            }
            }
            }


            限制線程優先級和調度

            Java 線程模型涉及可以動態更改的線程優先級。本質上,線程的優先級是從 1 到 10 之間的一個數字,數字越大表明任務越緊急。JVM 標準首先調用優先級較高的線程,然后才調用優先級較低的線程。但是,該標準對具有相同優先級的線程的處理是隨機的。如何處理這些線程取決于基層的操作系統策略。在某些情況下,優先級相同的線程分時運行;在另一些情況下,線程將一直運行到結束。請記住,Java 支持 10 個優先級,基層操作系統支持的優先級可能要少得多,這樣會造成一些混亂。因此,只能將優先級作為一種很粗略的工具使用。最后的控制可以通過明智地使用 yield() 函數來完成。通常情況下,請不要依靠線程優先級來控制線程的狀態。

            小結

            本文說明了在 Java 程序中如何使用線程。像是否應該使用線程這樣的更重要的問題在很大程序上取決于手頭的應用程序。決定是否在應用程序中使用多線程的一種方法是,估計可以并行運行的代碼量。并記住以下幾點:

            使用多線程不會增加 CPU 的能力。但是如果使用 JVM 的本地線程實現,則不同的線程可以在不同的處理器上同時運行(在多 CPU 的機器中),從而使多 CPU 機器得到充分利用。

            如果應用程序是計算密集型的,并受 CPU 功能的制約,則只有多 CPU 機器能夠從更多的線程中受益。

            當應用程序必須等待緩慢的資源(如網絡連接或數據庫連接)時,或者當應用程序是非交互式的時,多線程通常是有利的。

            基于 Internet 的軟件有必要是多線程的;否則,用戶將感覺應用程序反映遲鈍。例如,當開發要支持大量客戶機的服務器時,多線程可以使編程較為容易。在這種情況下,每個線程可以為不同的客戶或客戶組服務,從而縮短了響應時間。

            某些程序員可能在 C 和其他語言中使用過線程,在那些語言中對線程沒有語言支持。這些程序員可能通常都被搞得對線程失去了信心。

          posted @ 2009-12-05 17:32 睫晉姬 閱讀(125) | 評論 (0)編輯 收藏

          java RSA 加解密

            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 密鑰字符串(經過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 密鑰字符串(經過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;

            }
            /**

            * 得到密鑰字符串(經過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");

            //密鑰位數

            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);

            }

            }

          posted @ 2009-12-05 17:18 睫晉姬 閱讀(330) | 評論 (0)編輯 收藏

          Java虛擬機支持的最大內存限制

            最近在開發Java的程序。本來我是一直很喜歡Java的內存管理的,不需要擔心分配內存,只管分配,垃圾收集器自己會給你回收內存的?,F在開發的程序數據量很大,為了速度快,我準備把所有的信息加載進內存,這樣可以保證快速響應。我還在反復算內存,想想自己的數據量,現在剛開始的時候應該夠了(我的機器是4G內存,雖然Windows就認3.5G,但是比起我現在的數據量應該沒問題)。

            沒想到第一個實驗的程序,跑了幾個小時,就遇到了Out of Memory Exception了。看看自己的虛擬機設置,我設置的是-Xms512M -Xmx1024M。想都沒想,直接改成-Xms512M -Xmx2048M,結果直接就Could not reserve enough space for object heap。程序都起不來了。這才發現原來最大內存還有限制。上網搜了一下干洗機,發現很多討論這個問題的文章。最終在BEA的DEV2DEV論壇發現了最有用的一篇http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID= 35704&start=0&tstart=0

            這里的版主YuLimin 做了測試,得出結論:

            公司 JVM版本                  最大內存(兆)client    最大內存(兆)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

            我現在用的是JDK1.6. 0_05,測試了一下。在Client狀態下最大是,我的JDK不認-Server參數,測試不了Server狀態。估計差不多。

            SUN 1.6.0                          1442                           N/a

            看樣子用Java想用大內存也是不可能的了。而且一般的說法是內存太大了,垃圾收集的時間就會長。這也可以理解,一般是內存不夠用了才收集的,掃描2G內存比1G當然要慢多了,而且內存對象多了,估計關系是指數上升的。

            下面附上YuLimin的測試方法和測試記錄。

            測試方法:在命令行下用 java -XmxXXXXM -version 命令來進行測試,然后逐漸的增大XXXX的值,如果執行正常就表示指定的內存大小可用,否則會打印錯誤信息。

            測試記錄:

            我在Windows 2000 ADS上面測試內存使用的結果如下

            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’

          posted @ 2009-12-05 17:15 睫晉姬 閱讀(381) | 評論 (0)編輯 收藏

          JVM概念之Java對象的大小與引用類型

             基本數據的類型的大小是固定的,這里就不多說了。對于非基本類型的Java對象,其大小就值得商榷。

            在Java中,一個空Object對象的大小是8byte,這個大小只是保存堆中一個沒有任何屬性的對象的大小。看下面語句:

            Object ob = new Object();

            這樣在程序中完成了一個Java對象的生命,但是它所占的空間為:4byte+8byte。4byte是上面部分所說的Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對象的信息。因為所有的Java非基本類型的對象都需要默認繼承Object對象,因此不論什么樣的Java對象,其大小都必須是大于8byte。

            有了Object對象的大小,我們就可以計算其他對象的大小了。

            Class NewObject { int count; boolean flag; Object ob; }

            其大小為:空對象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因為Java在對對象內存分配時都是以8的整數倍來分,因此大于17byte的最接近8的整數倍的是24,因此此對象的大小為24byte。

            這里需要注意一下基本類型的包裝類型的大小。因為這種包裝類型已經成為對象了,因此需要把他們作為對象來看待。包裝類型的大小至少是12byte(聲明一個空Object至少需要的空間),而且12byte沒有包含任何有效衛星電視信息,同時,因為Java對象大小是8的整數倍,因此一個基本類型包裝類的大小至少是16byte。這個內存占用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內存占用更是夸張(隨便想下就知道了)。因此,可能的話應盡量少使用包裝類。在JDK5.0以后,因為加入了自動類型裝換,因此,Java虛擬機會在存儲方面進行相應的優化。

            引用類型

            對象引用類型分為強引用、軟引用、弱引用和虛引用。

            強引用:就是我們一般聲明對象是時虛擬機生成的引用,強引用環境下,垃圾回收時需要嚴格判斷當前對象是否被強引用,如果被強引用,則不會被垃圾回收

            軟引用:軟引用一般被做為緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩余內存來決定是否對軟引用進行回收。如果剩余內存比較緊張,則虛擬機會回收軟引用所引用的空間;如果剩余內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,肯定是沒有軟引用存在的。

            弱引用:弱引用與軟引用類似,都是作為緩存來使用。但與軟引用不同,弱引用在進行垃圾回收時,是一定會被回收掉的,因此其生命周期只存在于一個垃圾回收周期內。

            強引用不用說,我們系統一般在使用時都是用的強引用。而“軟引用”和“弱引用”比較少見。他們一般被作為緩存使用,而且一般是在內存大小比較受限的情況下做為緩存。因為如果內存足夠大的話,可以直接使用強引用作為緩存即可,同時可控性更高。因而,他們常見的是被使用在桌面應用系統的緩存。

          posted @ 2009-12-05 17:11 睫晉姬 閱讀(133) | 評論 (0)編輯 收藏

          Hibernate的五個核心接口介紹

            所有的Hibernate應用中都會訪問Hibernate的5個核心接口。

            Configuration接口:配置Hibernate,根啟動Hibernate,創建SessionFactory對象。

            SessionFactory接口:初始化Hibernate,充當數據存儲源的代理,創建Session對象。

            Session接口:負責保存、更新、刪除、加載和查詢對象。

            Transaction:管理事務。

            Query和Criteria接口:執行數據庫查詢。

            1.Configuration接口

            Configuration對象用于配置并且啟動Hibernate。Hibernate應用通過Configuration實例來指定對象-關系映射文件的位置或者動態配置Hibernate的屬性,然后創建SessionFactory實例。

            2.SessionFactory接口

            一個SessionFactory實例對應一個數據存儲源,應用從SessionFactory中獲得Session實例。SessionFactory有以下特點:

            它是線程安全的,這意味著它的同一個實例可以被應用的多個線程共享。

            它是重量級的,這意味著不能隨意創建或銷毀它的實例。如果應用只訪問一個數據庫,只需要創建一個SessionFactory實例,在應用初始化的時候創建該實例。如果應用同時訪問多個數據庫,則需要為每個干洗機創建數據庫創建一個單獨的SessionFactory實例。

            之所以稱SessionFactory是重量級的,是因為它需要一個很大的緩存,用來存放預定義的SQL語句以能映射元數據等。用戶還可以為SesionFactory配置一個緩存插件,這個緩存插件被稱為Hibernate的第二級緩存。,該緩存用來存放被工作單元讀過的數據,將來其他工作單元可能會重用這些數據,因此這個緩存中的數據能夠被所有工作單元共享。一個工作單元通常對應一個數據庫事務。

            3.Session接口

            Session接口是Hibernate應用使用最廣泛的接口。Session也被稱為持久化管理器,它提供了和持久化相關的操作,如添加、更新、刪除、加載和查詢對象。

            Session有以下特點:

            不是線程安全的,因此在設計軟件架構時,應該避免多個線程共享同一個Session實例。

            Session實例是輕量級的,所謂輕量級,是指它的創建和銷毀不需要消耗太多的資源。這意味著在程序中可以經常創建和銷毀Session對象,例如為每個客戶請示分配單獨的Session實例,或者為每個工作單元分配單獨的Session實例。

            Session有一個緩存,被稱為Hibernate的第一級緩存,它存放被當前工作單元加載的對象。每個Session實例都有自己的緩存,這個Sesion實例的緩存只能被當前工作單元訪問。

            4.Transaction接口

            Transaction接口是Hibernate的數據庫事務接口,它對底層的事務接口做了封裝,底層事務接口包括:

            JDBC API、JTA(Java Transaction API)、CORBA(Common Object Requet Broker Architecture)API

            Hibernate應用可通過一致的Transaction接口來聲明事務邊界,這有助于應用在不同的環境容器中移植。盡管應用也可以繞過Transaction接口,直接訪問底層的事務接口,這種方法不值得推薦,因為它不利于應用在不同的環境移植。

            5.Query和Criteria接口

            Query和Criteria接口是Hibernate的查詢接口,用于向數據庫查詢對象,以及控制執行查詢的過程。Query實例包裝了一個HQL查詢語句,HQL查詢語句和SQL查詢語句有些相似,但HQL查詢語句是面向對象的,它引用類句及類的屬性句,而不是表句及表的字段句。Criteria接口完全封裝了基于字符串的查詢語句,比Query接口更加面向對象,Criteria接口擅長執行動態查詢。

            Session接口的find()方法也具有數據查詢功能,但它只是執行一些簡單的HQL查詢語句的快捷方法,它的功能遠沒有Query接口強大。

          posted @ 2009-12-05 17:10 睫晉姬 閱讀(237) | 評論 (0)編輯 收藏

          幾種DispatchAction的區別

            java.lang.Object

            org.apache.struts.action.Action

            org.apache.struts.actions.DispatchAction

            org.apache.struts.actions.LookupDispatchAction(Struts1.1)

            org.apache.struts.actions.EventDispatchAction(Struts1.2.9)

            org.apache.struts.actions.MappingDispatchAction(Struts1.2)

            DispatchAction

            public abstract class DispatchAction extends Action

            這是一個抽象的Action,它會根據request 中的parameter來執行相應的方法。通個這個Action類可以將不同的Action集中到一個Action文件中來。

            struts-config.xml:

            <action path="/subscription" type="org.example.SubscriptionAction" name="subscriptionForm"  scope="request" input="/subscription.jsp" parameter="method"/>

            在Action中要有相應的方法:

            public class SubscriptionAction extends DispatchAction {

            public ActionForward delete(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward insert(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward update(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            }

            然后可以通過這樣的方法來訪問你的程序:

            http://localhost:8080/myapp/subscription.do?method=delete

            http://localhost:8080/myapp/subscription.do?method=insert

            http://localhost:8080/myapp/subscription.do?method=update

            如果parameter中參數為空,則調用Action中的unspecified方法

            LookupDispatchAction

            public abstract class LookupDispatchAction extends DispatchAction

            通過這個Action抽象類繼承DispatchAction,它的相應方法的執行由ActionMapping中parameter屬性決定。每個動作實際上就是<html:submit>標簽的property屬性值。它適合在一個form中有很多按鈕,按不同的按鈕則執行不同的操作。

            struts-config.xml:

            <action path="/subscription" type="org.example.SubscriptionAction" name="subscriptionForm" scope="request"

            input="/subscription.jsp" parameter="method"/>

            ApplicationResources.properties:

            button.add=Add Record

            button.delete=Delete Record

            JSP:

            <%@ page pageEncoding="GBK"%>

            <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>

            <html>

            <head>

            <title>多提交演示</title>

            </head>

            <body>

            <html:form action="subscription">

            <html:submit property="method">

            <bean:message key="button.add"/>

            </html:submit>

            <html:submit property="method">

            <bean:message key="button.delete"/>

            </html:submit>

            </html:form>

            </body>

            </html>
            在Action中必須實現getKeyMethodMap方法:

            public class SubscriptionAction extends LookupDispatchAction {

            protected Map getKeyMethodMap() {

            Map map = new HashMap();

            map.put("button.add", "add");

            map.put("button.delete", "delete");

            return map;

            }

            public ActionForward add(ActionMapping mapping,

            ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward delete(ActionMapping mapping,

            ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            }

            EventDispatchAction

            public class EventDispatchAction extends DispatchAction

            通過這個Action抽象類繼承DispatchAction,它的相應方法的執行由ActionMapping中parameter屬性指定多個動作,中間用逗號(,)分隔。每個動作實際上就是<html:submit>標簽的property屬性值。它適合在一個form中有很多按鈕,按不同的按鈕則執行不同的操作。

            struts-config.xml:

            (parameter中的"recalc=recalculate"意思為<html:submit>標簽的property屬性值為recalc調用recalculate方法,出于安全考慮能夠隱藏后臺的業務方法名。"defaule=save"這是可選的,沒有配置property屬性的<html:submit>標簽干洗機將調用defaule中設置的方法名,如果defaule沒有指定任何參數,則調用Action中的unspecified方法)

            <action path="/subscription" type="org.example.SubscriptionAction" name="subscriptionForm" scope="request"

            input="/subscription.jsp" parameter="save,back,recalc=recalculate,default=save"/>

            JSP:

            <%@ page pageEncoding="GBK"%>

            <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>

            <html>

            <head>

            <title>多提交演示</title>

            </head>

            <body>

            <html:form action="subscription">

            <html:submit property="save" value="保存"/>

            <html:submit property="back" value="后退"/>

            <html:submit property="recalc" value="重新計算"/>

            </html:form>

            </body>

            </html>

            在Action中要有相應的方法:

            public class SubscriptionAction extends LookupDispatchAction {

            public ActionForward save(ActionMapping mapping,

            ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward back(ActionMapping mapping,

            ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward recalculate(ActionMapping mapping,

            ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            }

            MappingDispatchAction

            public class MappingDispatchAction extends DispatchAction

            它的相應方法的執行由ActionMapping中parameter名決定,注意這里和LookupDispatchAction不同,LookupDispatchAction的相應方法的執行由ActionMapping中parameter屬性決定。

            struts-config.xml:

            <action path="/createSubscription" type="org.example.SubscriptionAction" parameter="create">

            <forward name="success" path="/createSubscription.jsp"/>

            </action>

            <action path="/editSubscription" type="org.example.SubscriptionAction" parameter="edit">

            <forward name="success" path="/editSubscription.jsp"/>

            </action>

            <action path="/saveSubscription" type="org.example.SubscriptionAction" parameter="save"

            name="subscriptionForm" validate="true" input="/editSubscription.jsp" scope="request">

            <forward name="success" path="/savedSubscription.jsp"/>

            </action>

            <action path="/deleteSubscription" type="org.example.SubscriptionAction" name="subscriptionForm"

            scope="request" input="/subscription.jsp" parameter="delete">

            <forward name="success" path="/deletedSubscription.jsp"/>

            </action>

            <action path="/listSubscriptions" type="org.example.SubscriptionAction" parameter="list">

            <forward name="success" path="/subscriptionList.jsp"/>

            </action>

            在Action中要有相應的方法:

            public class SubscriptionAction extends MappingDispatchAction {

            public ActionForward create(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward edit(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward save(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward delete(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            public ActionForward list(ActionMapping mapping, ActionForm form,

            HttpServletRequest request,

            HttpServletResponse response) throws Exception {}

            }

          posted @ 2009-12-05 16:59 睫晉姬 閱讀(220) | 評論 (0)編輯 收藏

          struts2中一個form多個提交的方法

            在很多Web應用中,為了完成不同的工作,一個HTML form標簽中可能有兩個或多個submit按鈕,如下面的代碼所示:

            <!--[if !supportLineBreakNewLine]-->

            <html action="" method="post">

            <input type="submit" value="保存" />

            <input type="submit" value="打印" />

            </html>

            由于在<form>中的多個提交按鈕都向一個action提交,使用Struts2 Action的execute方法就無法判斷用戶點擊了哪一個提交按鈕。如果大家使用過Struts1.x就會知道在Struts1.2.9之前的版本需要使用一個LookupDispatchAction動作來處理含有多個submit的form。但使用LookupDispatchAction動作需要訪問屬性文件,還需要映射,比較麻煩。從Struts1.2.9開始,加入了一個EventDispatchAction動作。這個類可以通過java 反射來調用通過request參數指定的動作(實際上只是判斷某個請求參數是不存在,如果存在,就調用在action類中和這個參數同名的方法)。使用 EventDispatchAction必須將submit的name屬性指定不同的值以區分每個submit。而在Struts2中將更容易實現這個功能。

            當然,我們也可以模擬EventDispatchAction的方法通過request獲得和處理參數信息。但這樣比較麻煩。在Struts2中提供了另外一種方法,使得無需要配置可以在同一個action類中執行不同的方法(默認執行的是execute方法)。使用這種方式也需要通過請求參來來指定要執行的動作。請求參數名的格式為

            action!method.action

            注:由于Struts2只需要參數名,因此,參數值是什么都可以。

            下面我就給出一個實例程序來演示如何處理有多個submit的form:

            【第1步】實現主頁面(more_submit.jsp)

            <%@ page language="java" import="java.util.*" pageEncoding="GBK"%>

            <%@ taglib prefix="s" uri="/struts-tags" %>

            <html>

            <head>

            <title>My JSP 'hello.jsp' starting page</title>

            </head>

            <body>

            <s:form action="submit.action" >

            <s:textfield name="msg" label="輸入內容"/>

            <s:submit name="save" value="保存" align="left" method="save"/>

            <s:submit name="print" value="打印" align="left" method="print" />

            </s:form>

            </body>

            </html>

            在more_submit.jsp中有兩個submit:保存和打印。其中分別通過method屬性指定了要調用的方法:save和print。因此,在Action類中必須要有save和print方法。

            【第2步】實現Action類(MoreseoSubmitAction)

            package action;

            import javax.servlet.http.*;

            import com.opensymphony.xwork2.ActionSupport;

            import org.apache.struts2.interceptor.*;

            public class MoreSubmitAction extends ActionSupport implements

            ServletRequestAware {

            private String msg;

            private javax.servlet.http.HttpServletRequest request;

            // 獲得HttpServletRequest對象

            public void setServletRequest(HttpServletRequest request) {

            this.request = request;

            }

            // 處理save submit按鈕的動作

            public String save() throws Exception {

            request.setAttribute("result", "成功保存[" + msg + "]");

            return "save";

            }

            // 處理print submit按鈕的動作

            public String print() throws Exception {

            request.setAttribute("result", "成功打印[" + msg + "]");

            return "print";

            }

            public String getMsg() {

            return msg;

            }

            public void setMsg(String msg) {

            this.msg = msg;

            }

            }

            上面的代碼需要注意如下兩點:

            save和print方法必須存在,否則會拋出java.lang.NoSuchMethodException異常。

            Struts2 Action動作中的方法和Struts1.x Action的execute不同,只使用Struts2 Action動作的execute方法無法訪問request對象,因此,Struts2 Action類需要實現一個Struts2自帶的攔截器來獲得request對象,攔截器如下:

            org.apache.struts2.interceptor. ServletRequestAware

            【第3步】配置Struts2 Action

            struts.xml的代碼如下:

            <?xml version="1.0" encoding="UTF-8" ?>

            <!DOCTYPE struts PUBLIC

            "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

            "http://struts.apache.org/dtds/struts-2.0.dtd">

            <struts>

            <package name="demo" extends="struts-default" >

            <action name="submit" class="action.MoreSubmitAction">

            <result name="save" >

            /result.jsp

            </result>

            <result name="print">

            /result.jsp

            </result>

            </action>

            </package>

            </struts>

            【第4步】編寫結果頁(result.jsp) <%@ page pageEncoding="GBK"%>

            <html>

            <head>

            <title>提交結果</title>

            </head>

            <body>

            <h1>${result}</h1>

            </body>

            </html>

            在result.jsp中將在save和print方法中寫到request屬性中的執行結果信息取出來,并輸出到客戶端。

          posted @ 2009-12-05 16:34 睫晉姬 閱讀(122) | 評論 (0)編輯 收藏

          Struts2 標簽

            用過struts1.x的人都知道,標簽庫有html、bean、logic、tiles,而struts2.0里的標簽卻沒有分類,只用在jsp頭文件加上

            <%@ taglib prefix="s" uri="/struts-tags" %>

            就能使用struts2.0的標簽庫

            A:

            <s:a href=""></s:a>-----超鏈接,類似于html里的<a></a>

            <s:action name=""></s:action>-----執行一個view里面的一個action

            <s:actionerror/>-----如果action的errors有值那么顯示出來

            <s:actionmessage/>-----如果action的message有值那么顯示出來

            <s:append></s:append>-----添加一個值到list,類似于list.add();

            <s:autocompleter></s:autocompleter>-----自動完成<s:combobox>標簽的內容,這個是ajax

            B:

            <s:bean name=""></s:bean>-----類似于struts1.x中的,JavaBean的值

            C:

            <s:checkbox></s:checkbox>-----復選框

            <s:checkboxlist list=""></s:checkboxlist>-----多選框

            <s:combobox list=""></s:combobox>-----下拉框

            <s:component></s:component>-----圖像符號

            D:

            <s:date/>-----獲取日期格式

            <s:datetimepicker></s:datetimepicker>-----日期輸入框

            <s:debug></s:debug>-----顯示錯誤信息

            <s:div></s:div>-----表示一個塊,類似于html的<div></div>

            <s:doubleselect list="" doubleName="" doubleList=""></s:doubleselect>-----雙下拉框

            E:

            <s:if test=""></s:if>

            <s:elseif test=""></s:elseif>

            <s:else></s:else>-----這3個標簽一起使用,表示條件判斷

            F:

            <s:fielderror></s:fielderror>-----顯示文件錯誤信息

            <s:file></s:file>-----文件上傳

            <s:form action=""></s:form>-----獲取相應form的值

            G:

            <s:generator separator="" val=""></s:generator>----和<s:iterator>標簽一起使用

            H:

            <s:head/>-----在<head></head>里使用,表示頭文件結束

            <s:hidden></s:hidden>-----隱藏值

            I:

            <s:i18n name=""></s:i18n>-----加載資源包到值堆棧

            <s:include value=""></s:include>-----包含一個輸出,servlet或jsp頁面

            <s:inputtransferselect list=""></s:inputtransferselect>-----獲取form的一個輸入

            <s:iterator></s:iterator>-----用于遍歷集合

            L:

            <s:label></s:label>-----只讀的標簽

            M:

            <s:merge></s:merge>-----合并遍歷集合出來的值

            O:

            <s:optgroup></s:optgroup>-----獲取標簽組

            <s:optiontransferselect doubleList="" list="" doubleName=""></s:optiontransferselect>-----左右選擇框

            P:

            <s:param></s:param>-----為其他標簽提供參數

            <s:password></s:password>-----密碼輸入框

            <s:property/>-----得到'value'的屬性

            <s:push value=""></s:push>-----value的值push到棧中,從而使property標簽的能夠獲取value的屬性

            R:

            <s:radio list=""></s:radio>-----單選按鈕

            <s:reset></s:reset>-----重置按鈕

            S:

            <s:select list=""></s:select>-----單選框

            <s:set name=""></s:set>-----賦予變量一個特定范圍內的值

            <s:sort comparator=""></s:sort>-----通過屬性給list分類

            <s:submit></s:submit>-----提交按鈕

            <s:subset></s:subset>-----為遍歷集合輸出子集

            T:

            <s:tabbedPanel id=""></s:tabbedPanel>-----表格框

            <s:table></s:table>-----表格

            <s:text name=""></s:text>-----I18n文本信息

            <s:textarea></s:textarea>-----文本域輸入框

            <s:textfield></s:textfield>-----文本輸入框

            <s:token></s:token>-----攔截器

            <s:tree></s:tree>-----樹

            <s:treenode label=""></s:treenode>-----樹的結構

            U:

            <s:updownselect list=""></s:updownselect>-----多選擇框

            <s:url></s:url>-----創建url

          posted @ 2009-12-05 16:01 睫晉姬 閱讀(118) | 評論 (0)編輯 收藏

          AJAX和XMLHTTP原理

            Ajax的原理簡單來說通過XmlHttpRequest對象來向服務器發異步請求,從服務器獲得數據,然后用javascript來操作DOM而更新頁面。這其中最關鍵的一步就是從服務器獲得請求數據。要清楚這個過程和原理,我們必須對 XMLHttpRequest有所了解。

            XMLHttpRequest是ajax的核心機制,它是在IE5中首先引入的,是一種支持異步請求的技術。簡單的說,也就是javascript可以及時向服務器提出請求和處理響應,而不阻塞用戶。達到無刷新的效果。

            所以我們先從XMLHttpRequest講起,來看看它的工作原理。

            首先,我們先來看看XMLHttpRequest這個對象的屬性。

            它的屬性有:

            onreadystatechange 每次狀態改變所觸發事件的事件處理程序。

            responseText     從服務器進程返回數據的字符串形式。

            responseXML    從服務器進程返回的DOM兼容的文檔數據對象。

            status           從服務器返回的數字代碼,比如常見的404(未找到)和200(已就緒)

            status Text       伴隨狀態碼的字符串信息

            readyState       對象狀態值

            0 (未初始化) 對象已建立,但是尚未初始化(尚未調用open方法)

            1 (初始化) 對象已建立,尚未調用send方法

            2 (發送數據) send方法已調用,但是當前的狀態及http頭未知

            3 (數據傳送中) 已接收部分數據,因為響應及http頭不全,這時通過responseBody和responseText獲取部分數據會出現錯誤,

            4 (完成) 數據接收完畢,此時可以通過通過responseXml和responseText獲取完整的回應數據

            但是,由于各瀏覽器之間存在差異,所以創建一個XMLHttpRequest對象可能需要不同的方法。這個差異主要體現在IE和其它瀏覽器之間。下面是一個比較標準的創建XMLHttpRequest對象的方法。

            function CreateXmlHttp()

            {

            //非IE瀏覽器創建XmlHttpRequest對象

            if(window.XmlHttpRequest)

            {

            xmlhttp=new XmlHttpRequest();

            }

            //IE瀏覽器創建XmlHttpRequest對象

            if(window.ActiveXObject)

            {

            try

            {

            xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");

            }

            catch(e)

            {

            try{

            xmlhttp=new ActiveXObject("msxml2.XMLHTTP");

            }

            catch(ex){}

            }

            }

            }
            function Ustbwuyi()

            {

            var data=document.getElementById("username").value;

            CreateXmlHttp();

            if(!xmlhttp)

            {

            alert("創建xmlhttp對象異常!");

            return false;

            }

            xmlhttp.open("POST",url,false);

            xmlhttp.onreadystatechange=function()

            {

            if(xmlhttp.readyState==4)

            {

            document.getElementById("user1").innerHTML="數據正在加載...";

            if(xmlhttp.status==200)

            {

            document.write(xmlhttp.responseText);

            }

            }

            }

            xmlhttp.send();

            }

            如上所示,函數首先檢查XMLHttpRequest的整體狀態并且保證它已經完成(readyStatus=4),即數據已經發送完畢。然后根據服務器的設定詢問請求狀態,如果一切已經就緒(status=200),那么就執行下面需要的操作。

            對于XmlHttpRequest的兩個方法,open和send,其中open方法指定了:

            a、向服務器提交數據的類型,即post還是get。

            b、請求的url地址和傳遞的參數。

            c、傳輸方式,false為同步,true為異步。默認為true。如果是異步通信方式(true),客戶機就不等待服務器的響應;如果是同步方式(false),客戶機就要等到服務器返回消息后才去執行其他操作。我們需要根據網站優化實際需要來指定同步方式,在某些頁面中,可能會發出多個請求,甚至是有組織有計劃有隊形大規模的高強度的request,而后一個是會覆蓋前一個的,這個時候當然要指定同步方式。

            Send方法用來發送請求。

            知道了XMLHttpRequest的工作流程,我們可以看出,XMLHttpRequest是完全用來向服務器發出一個請求的,它的作用也局限于此,但它的作用是整個ajax實現的關鍵,因為ajax無非是兩個過程,發出請求和響應請求。并且它完全是一種客戶端的技術。而XMLHttpRequest正是處理了服務器端和客戶端通信的問題所以才會如此的重要。

            現在,我們對ajax的原理大概可以有一個了解了。我們可以把服務器端看成一個數據接口,它返回的是一個純文本流,當然,這個文本流可以是XML格式,可以是Html,可以是Javascript代碼,也可以只是一個字符串。這時候,XMLHttpRequest向服務器端請求這個頁面,服務器端將文本的結果寫入頁面,這和普通的web開發流程是一樣的,不同的是,客戶端在異步獲取這個結果后,不是直接顯示在頁面,而是先由javascript來處理,然后再顯示在頁面。至于現在流行的很多ajax控件,比如magicajax等,可以返回DataSet等其它數據類型,只是將這個過程封裝了的結果,本質上他們并沒有什么太大的區別。

          posted @ 2009-12-05 15:54 睫晉姬 閱讀(124) | 評論 (0)編輯 收藏

          Java線程:線程的同步-同步方法

              線程的同步是保證多線程安全訪問競爭資源的一種手段。

              線程的同步是Java多線程編程的難點,往往開發者搞不清楚什么是競爭資源、什么時候需要考慮同步,怎么同步等等問題,當然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?
           
              在本文之前,請參閱《Java線程:線程的同步與鎖》,本文是在此基礎上所寫的。
           
              對于同步,在具體的Java代碼中需要完成一下兩個操作:

              把競爭訪問的資源標識為private;

              同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。

              當然這不是唯一控制并發安全的途徑。
           
              synchronized關鍵字使用說明

              synchronized只能標記非抽象的方法,不能標識成員變量。
           
              為了演示同步方法的使用,構建了一個信用卡賬戶,起初信用額為100w,然后模擬透支、存款等多個操作。顯然銀行賬戶User對象是個競爭資源,而多個并發操作的是賬戶方法oper(int x),當然應該在此方法上加上同步,并將賬戶的余額設為私有變量,禁止直接訪問。
           
           
          /**
          * Java線程:線程的同步
          *
          * @author leizhimin 2009-11-4 11:23:32
          */
          public class Test {
                  public static void main(String[] args) {
                          User u = new User("張三", 100);
                          MyThread t1 = new MyThread("線程A", u, 20);
                          MyThread t2 = new MyThread("線程B", u, -60);
                          MyThread t3 = new MyThread("線程C", u, -80);
                          MyThread t4 = new MyThread("線程D", u, -30);
                          MyThread t5 = new MyThread("線程E", u, 32);
                          MyThread t6 = new MyThread("線程F", u, 21);

                          t1.start();
                          t2.start();
                          t3.start();
                          t4.start();
                          t5.start();
                          t6.start();
                  }
          }

          class MyThread extends Thread {
                  private User u;
                  private int y = 0;

                  MyThread(String name, User u, int y) {
                          super(name);
                          this.u = u;
                          this.y = y;
                  }

                  public void run() {
                          u.oper(y);
                  }
          }

          class User {
                  private String code;
                  private int cash;

                  User(String code, int cash) {
                          this.code = code;
                          this.cash = cash;
                  }

                  public String getCode() {
                          return code;
                  }

                  public void setCode(String code) {
                          this.code = code;
                  }

                  /**
                   * 業務方法
                   * @param x 添加x萬元
                   */
                  public synchronized void oper(int x) {
                          try {
                                  Thread.sleep(10L);
                                  this.cash += x;
                                  System.out.println(Thread.currentThread().getName() + "運行結束,增加“" + x + "”,當前用戶賬戶余額為:" + cash);
                                  Thread.sleep(10L);
                          } catch (InterruptedException e) {
                                  e.printStackTrace();
                          }
                  }

                  @Override
                  public String toString() {
                          return "User{" +
                                          "code='" + code + '\'' +
                                          ", cash=" + cash +
                                          '}';
                  }
          }
           
              輸出結果:

          線程A運行結束,增加“20”,當前用戶賬戶余額為:120
          線程F運行結束,增加“21”,當前用戶賬戶余額為:141
          線程E運行結束,增加“32”,當前用戶賬戶余額為:173
          線程C運行結束,增加“-80”,當前用戶賬戶余額為:93
          線程B運行結束,增加“-60”,當前用戶賬戶余額為:33
          線程D運行結束,增加“-30”,當前用戶賬戶余額為:3

          Process finished with exit code 0
           
              反面教材,不同步的情況,也就是去掉oper(int x)方法的synchronized修飾符,然后運行程序,結果如下:

          線程A運行結束,增加“20”,當前用戶賬戶余額為:61
          線程D運行結束,增加“-30”,當前用戶賬戶余額為:63
          線程B運行結束,增加“-60”,當前用戶賬戶余額為:3
          線程F運行結束,增加“21”,當前用戶賬戶余額為:61
          線程E運行結束,增加“32”,當前用戶賬戶余額為:93
          線程C運行結束,增加“-80”,當前用戶賬戶余額為:61

          Process finished with exit code 0
           
              很顯然,上面的結果是錯誤的,導致錯誤的原因是多個線程并發訪問了競爭資源u,并對u的屬性做了改動。
           
              可見同步的重要性。
           
              注意:

              通過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應該注意的是,同步方法中還可以使用特定的方法對線程進行調度。這些方法來自于java.lang.Object類。
           
          void notify()   
                              喚醒在此對象監視器上等待的單個線程。   
          void notifyAll()   
                              喚醒在此對象監視器上等待的所有線程。   
          void wait()   
                              導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法。   
          void wait(long timeout)   
                              導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量。   
          void wait(long timeout, int nanos)   
                              導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。
           
              結合以上方法,處理多線程同步與互斥問題非常重要,著名的生產者-消費者例子就是一個經典的例子,任何語言多線程必學的例子。

          posted @ 2009-12-05 15:50 睫晉姬 閱讀(124) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 凤冈县| 平武县| 汕尾市| 西青区| 新昌县| 治多县| 仪陇县| 衡水市| 郯城县| 中牟县| 双牌县| 长宁县| 丰都县| 虞城县| 宁国市| 水城县| 鄄城县| 佳木斯市| 榆树市| 东乌| 和平区| 米易县| 沂源县| 隆尧县| 南江县| 大连市| 六盘水市| 宣城市| 合阳县| 新野县| 河北省| 金乡县| 镇坪县| 岱山县| 雷波县| 尼玛县| 太保市| 交口县| 威海市| 尉氏县| 广昌县|