摘要
在本章中,我們將了解更多的關於Java的知識,包括用於訪問數據庫的JDBC、以及Java的網絡編程、以及JavaBeans等Java高級特性。通過本章的學習,大家應該能夠了解這些知識的概念,以便今後更好地學習Java語言。
(2002-09-02 13:38:18)
--------------------------------------------------------------------------------
By Wing, 出處:fjxufeng
本章目標:
在本章中,我們將了解更多的關於Java的知識,包括用於訪問數據庫的JDBC、以及Java的網絡編程、以及JavaBeans等Java高級特性。通過本章的學習,大家應該能夠了解這些知識的概念,以便今後更好地學習Java語言。
13.1 什麼是JDBC
傳授新知
人類已經走進信息時代,信息的獲取、整理工作越來越重要,從傳統業務電子化、自動化到決策支持等方方面面都離不開信息的處理。
為了能夠更好地處理信息,就需要更好地收集、存儲信息。這也是計算機領域中一個十分重要的分支學科:數據庫系統。由於其巨大的市場空間,有許許多多的計算機軟件公司致力於研究、開發各種功能強勁的數據庫系統。從適合於桌型應用的FoXPro、DBASE,到大型數據庫系統MicroSoft SQL Server、Oracle、Sybase、IBM DB2、Infomix等,它們雖然都是遵造SQL語言打造而成的,但是由於不同的開發廠商、不同產品特點、不同的市場針對性,使得它們的接口、使用方法等都有許多差別。這使得我們不得不研究這些不同的地方,以便能夠在應用中靈活使用。而假如我們在開發計算機應用系統時需要根據不同的數據庫系統進行專門的設計,必將使得程序的通用性、靈活性、可維護性大大降低。
13.1.1 ODBC
為了使數據庫系統的應用開發能夠更加輕松,微軟公司提供了一套數據庫接口規范ODBC(對,你沒看錯是ODBC,為了大家能夠更加清楚地熟悉JDBC,我們先給大家介紹一下ODBC)。正如下圖所示,它為編程語言訪問數據庫提供了一個標准接口:
圖13-1 ODBC結構示意圖
我們現在一起來看一下這張圖,當應用程序(屬於應用程序層)需要對數據庫進行操作時,我們就通過ODBC接口來實現與數據庫進行打交道。由於ODBC接口是統一的、是標准的,所以所有使用ODBC接口訪問、操作數據庫的程序的寫法都是一樣的。
那麼ODBC接口又是如何完成實際的數據庫訪問和操作的呢!它是通過各種數據庫系統所提供的ODBC驅動程序來完成了!
由於支持ODBC的數據庫十分多,所以一個系統中ODBC驅動程序有許多個,因此ODBC需要一個治理這些ODBC驅動程序的治理器。
整個過程就是,應用程序通過ODBC接口來訪問數據庫,並且無需關心是什麼數據庫;而當ODBC接口收到這個數據庫操作請求時,就通過驅動程序治理器找到相應的數據庫ODBC驅動程序;最後ODBC驅動程序連接數據庫完成操作。
ODBC的出現使得應用程序訪問、操作數據庫更加輕易,迅速成為了一個通用的標准。而Java做為了一種網絡化的編程語言,也少不了要與數據庫打交道,要讓Java程序訪問數據庫更加輕易,也必須提供相應的機制把不同數據庫系統所帶來的差異屏蔽掉。因此,就導致了JDBC(Java DataBase Connectivity)的誕生。
13.1.2 JDBC
看到這裡,大家也許會問既然有了ODBC,為什麼要有JDBC呢?其實原因很簡單,ODBC主要是對數據庫系統的C語言接口,不太輕易被Java使用;再者它是Microsoft的標准,Microsoft何許人也,Java締造者Sun公司的死對頭呀!
因此,Sun公司重新設計了一個適合於Java使用的數據庫操作引擎:JDBC,並於1996年3月份發布。它沿襲了ODBC的設計思想,並且采用了與ODBC一樣的設計基礎:X/Open SQL Call Level Interface(簡稱CLI)。為了使JDBC更加Power,還開發了一個“JDBC-ODBC的翻譯器”(也被稱為橋接器),通過它,還可以使用數據庫的ODBC驅動程序。
JDBC定義了一套API對象和方法,用來訪問、操作數據庫系統。在一個使用JDBC的Java程序中,首先將打開一個數據庫的連接,生成一個語句(Statement)對象,使用這個對象將SQL語句傳送給數據庫系統,然後用它檢索結果。
JDBC類包含在java.sql包中,我們可以使用這個包中的對象和方法從數據庫讀取數據或寫入數據。正如下圖所示,JDBC提供了兩種不同的訪問數據庫的機器:
1)
圖13-2 通過JDBC-ODBC橋接器訪問數據庫
在這種情況下,JDBC通過訪問一個本地模塊JDBC-ODBC橋接器(JDBCODBC.DLL)來與數據庫交互。這個數據庫可以在本地,也可以在遠程。
一些說明:
由於ODBC在ISV(Independent Software Vendor,獨立軟件開發商)和用戶中非常流行,有許多數據庫都提供了ODBC驅動程序,因此Sun公司為了利用這些可用的ODBC驅動程序,就開發了一個橋接器。這是一種很成功的市場行為。
2)
圖13-3 通過HTTP偵聽程序訪問數據庫
在這種情況下,Java程序通過使用一些RPC或一個HTTP發送者-偵聽者協議與網絡上的數據庫服務器交談,實現數據庫的訪問與操作。
一般來說,JDBC類文件和Java小應用程序是存放在客戶端,用來訪問位於遠程服務器上的數據庫。
3)
圖13-4 JDBC示意圖(3)
還有一種結構JDBC通過數據庫的JDBC驅動程序來訪問數據庫,當然就需要數據庫的支持,例如Oracle就有JDBC驅動程序。
隨著JDBC的使用越來越廣泛,對JDBC提供支持也會成為數據庫廠商參與市場競爭的一個重要手段,所以今後會有越來越多的數據庫會提供JDBC驅動程序。
一些提示:
關於JDBC的更多內容可以參考四個與JDBC規范有關的重要文檔:JDBC規范、JDBC API文檔I-JDBC接口、JDBC API文檔II-類和異常以及JDK1.1文檔的JDBC手冊。
你可以在Sun的www.javasoft.com中找到。文件名分別為:jdbc.spec-0122.pdf、jdbc.api.1-0122.pdf、jdbc.api.2-0122.pdf、jdbc.pdf。
要想成為JDBC方面的專家,或想更深入地研究它們,請下載並刻苦研讀它們。
自測練習
1) Java語言提供的訪問、操作數據庫的統一接口稱為__________。
a.ODBC b.JDBC c.DBAP
2) Java語言中提供的這個數據庫接口包含在__________包中。
a.java.odbc b.java.jdbc c.java.sql d.java.dbap
3) Java數據庫程序通常是在網絡上運行的,那麼JDBC類一般位於_________。
a.客戶端 b.服務器端 c.兩端都有其中一部分
4) JDBC可以通過____________來使用數據庫的ODBC驅動程序。
a.jdbcodbc.dll b.ODBC模擬器 c.沒有條件,可以直接使用
5) ODBC與JDBC有一個共同的設計基礎,它是______________。
a.SQL標准 b.CLI
6)__________發布時間更早。
a.JDBC b.ODBC c.DBAP
7)使用ODBC訪問數據庫,數據庫只能夠在主機上。__________
a.對 b.不對
8)在ODBC模型中,最終是通過________來操作數據庫的。
a.ODBC接口 b.驅動程序治理器 c.數據庫的ODBC驅動程序
9)而在JDBC模型中,在應用程序中,我們是通過_________來使用數據庫的。
a.JDBC-ODBC橋接器 b.JDBC接口 c.ODBC驅動程序
練習答案
1)b 當然是JDBC,Java DataBase Connectivity。另外,並不存在一種名為DBAP的數據庫接口API。
2)c 首先不可能會是java.odbc,也不會是java.dbap,也許你最想選擇的可能會是java.jdbc,可是Java類的命名者卻使用了數據庫聖經的名字(SQL)命名了。
3)a 通常是在客戶端。
4)a 使用JDBC-ODBC的轉換器,這個轉換器的文件名就是jdbc-odbc.dll。
5)b 這兩個數據庫接口使用了同一個設計基礎,X/Open SQL Call Level Interface,它簡稱為CLI。
6)b ODBC比JDBC早很長一段時間。
7)b 當然不對,ODBC也是答應訪問遠程服務器上的數據庫的。
8)c 是通過真正能夠了解數據庫結構的,由數據庫廠商提供的,專門的ODBC驅動程序來完成的。
9)b 對於應用程序而言,它只需直接調用提供的接口就可以了,無須去關心具體的數據庫實現。
13.2 用JDBC連接數據庫
傳授新知
所有的JDBC類和方法都包含在java.sql包中:
類別 類
驅動程序 java.sql.Driver
java.sql.DriverManager
java.sql.DrivePropertyInfo
連接 java.sql.Connection
語句 java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
結果集 java.sql.ResultSet
錯誤/警告 java.sql..SQLException
java.sql.SQLWarning
元數據 java.sql.DatabaseMetaData
java.sql.ResultSetMetaData
日期/時間 java.sql.Date
java.sql.Time
java.sql.Timestamp
其它 java.sql.Types
java.sql.DataTruncation
表13-1 JDBC類一覽表
在使用JDBC時,每一個驅動程序要被加載之前,都要使用DriverManager進行注冊。當需要打開一個連接時,DriverManager根據JDBC的URL選擇相應的驅動程序。
同樣的,JDBC也體現了Java這一網絡語言的特點,它也是使用URL進行識別一個數據庫的。它的語法格式是:
jdbc:<子協議>:<與DBMS/Protocol相關的子名字>
JDBC的URL分三個部分組成:
1)指示部分:jdbc。請大家回憶一下,一個常見的WEB頁面的URL是以什麼開頭的?對,http://,這個指示部分用來說明這個URL是什麼類別的。
2)子協議部分,這個部分用來指出JDBC的子協議,如odbc;
3)子名字:數據源的名字,假如數據庫是在網絡上的,那麼就用URL//hostname:port//來表示。
例如:jdbc:odbc:;User=<用戶名>;PW=<口令>
使用JDBC的整個流程如下圖所示:
圖13-5 JDBC調用流程圖
正如上圖所示,使用JDBC訪問數據庫需要經過以下幾個步驟:
1) 首先,程序調用getConnection()方法,獲得一個Connection對象;
2) 然後程序創建一個Statement對象並預備一個SQL語句;這個SQL語句可以是:
a. 被立即執行的語句:Statement對象;
b.被編譯的語句:PreparedStatement對象;
c.存儲過程的調用:CallableStatement對象。
3) 接著我們就可以執行這個語句(如executeQuery( )、execute( ))等,將得到一個存儲結果的ResultSet對象。
注重:
也有一些語句是完成一些事務工作,如executeUpdate(),更新。它們將不會返回ResultSet對象,因為它們並沒有結果。
4) 最後,我們就可以使用類似next()等方法對返回的ResultSet對象進行相應的處理。
實例說明
下面,我們來看一個簡單的例子。由於JDBC是用來訪問數據庫的,所以我們首先來創建一個數據庫。為了大家都能夠完成這個實驗,我們就用Office套件中的Access數據庫來完成這個例子。
1. 創建Access數據庫student.mdb,在這個庫中創建一個表student:
圖13-6 數據庫student的表結構
這個表student的內容如下圖所示:
圖13-7 表student的內容
2. 當我們建好這個示例數據庫student後,我們要為其設置ODBC:
一些說明:
讀到這裡,可能有些讀者會感到希奇,我們不是在使用JDBC嗎?為什麼要設置ODBC呢?這是因為有帶JDBC驅動程序的數據庫沒有ODBC那樣多?我們在這裡使用的數據庫系統Access就沒有相應的JDBC驅動程序。
不過沒關系,大家應該記得在JDBC中有一個JDBC-ODBC的橋接器,通過它就可以使用ODBC驅動程序啦。
現在應該明白為什麼為什麼要先設置ODBC了吧!
設置ODBC的方法很簡單:
1) 首先,我們打開控制面板,雙擊“ODBC數據源(32位)”按鈕,然後選擇“系統DSN頁”,將出現如下界面:
圖13-8 ODBC設置(1)
2) 然後,我們單擊“添加”按鈕,選擇“Microsoft Access Driver”,然後單擊“完成”按鈕:
圖13-9 ODBC設置(2)
3) 接著,就會出現一個如下圖所示的對話框,我們首先填上 “數據源名”:student,然後單擊“選取”按鈕,選擇我們剛才創建的數據庫student.mdb。然後單擊“確定“按鈕,至此ODBC設置完成:
13-10 ODBC設置(3)
3. 然後,創建一個源程序文件testjdbc.java,其內容如下:
源程序:testjdbc.java
import java.sql.*;
public class testjdbc
{
public static void main(String args[]) throws SQLException
{
try
{
String name,sex,age,chinese,maths,output;
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection
("jdbc:odbc:student");
Statement sta=con.createStatement();
ResultSet rs=sta.executeQuery
("SELECT Name,Sex,Age,Chinese,Maths FROM student");
System.out.println("Name Sex Age Chinese Maths");
while(rs.next())
{
name=rs.getString(1);
sex=rs.getString(2);
age=rs.getString(3);
chinese=rs.getString(4);
maths=rs.getString(5);
output=name+" "+sex+" "+age+" "+chinese+" "+maths;
System.out.println(output);
}
}
catch(java.lang.Exception ex)
{
ex.printStackTrace();
}
}
}
4. 最後,我們使用javac編譯,然後執行java testjdbc,程序輸出如下所示:
圖13-11 程序輸出圖
傳授新知
下面,我們就一起來學習一下這個程序:
1)
import java.sql.*;
我們知道,JDBC的所有類和方法都在java.sql包中實現的,所以當我們的程序中要使用到JDBC時,就要把這個包import進來。否則就會出錯的喲。
2)
public static void main(String args[]) throws SQLException
這個語句,我想大家應該可以看懂一半,也就是直到throws之前的部分,這是定義了main方法。而後面的throws SQLException則是說明假如程序碰到SQLException錯誤時就忽略錯誤。
3)
try
{
……
}
catch(java.lang.Exception ex)
{
ex.printStackTrace();
}
這個語句,我們曾經在前面一章中做過介紹,也就是在try程序段中,假如碰到catch語句中定義的錯誤(在這裡是java.lang.Exception ex),就執行catch程序段(在這裡就是執行ex.printStackTrace(),打印出相關的錯誤信息)。
4)
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
這條語句,大家雖然沒有見過,但我想大家都能夠猜出它的用途來!對,就是初始化並加載JDBC-ODBC驅動程序。
在JDBC的程序中使用JDBC-ODBC橋接器,就一定要在最前面加上這麼一句。
5)
Connection con=DriverManager.getConnection
("jdbc:odbc:student");
這條語句中,我們定義了一個Connection類的對象con,用來存放getConnection方法返回的數據庫連接。
大家回想一下圖13-5,在最上層,我們是通過“驅動程序治理器”發出一個getConnection方法,以獲得一個Connection(數據庫連接)。
getConnection方法的語法格式是:
Connection getConnection(String url,String user,String passWord);
它可以帶上三個參數:
<1> url:數據庫的URL。我們知道JDBC的URL由三個部分組成:
圖13-12 URL分析
<2> user:用戶名。象SQL Server、Oracle…等大型數據庫中,均采用了用戶治理,而在Access中並未使用,所以在本例中當然就不用指出相應的用戶了;
<3> password:相應用戶的口令,它是與用戶名對應的。
6)Statement sta=con.createStatement();
我們繼續參考圖13-5,獲得了一個Connection(與數據庫的連接)後,我們應該創建一個語句(有三種方法),在我們的程序中使用了最常使用的語句創建方法:createStatement。
注重:
創建語句所引用的方法createStatement是Connection對象的方法!
這樣,我們就得到了一個Statement(語句)對象sta。
7)
ResultSet rs=sta.executeQuery
("SELECT Name,Sex,Age,Chinese,Maths FROM student");
根據圖13-5的指示,接下來,我們要使用Statement對象的方法來執行SQL語句,在本例中,我們使用了一個最常用的executeQuery方法。
這個方法所帶的參數是一個SQL語句。
小知識:SQL語言
SQL用來組織、治理和檢索存儲在計算機數據庫中的數據。它的英文全稱是:“StrUCtured Query Language”。它是所有的關系型數據庫均采用的標准。是一種專門用於數據庫操作的語言。它可嵌入到其它語言中(如這裡)來實現數據庫操作。
例如,這裡的SELECT Name,Sex,Age,Chinese,Maths FROM student的意思就是從student表中選擇出Name,Sex,Age,Chinese,Maths五個字段。
SQL語言博大精深,而且十分有用,有愛好的讀者可以去閱讀《SQL完全參考手冊》(上、下)。在此限於篇幅就不帶贅述了。
執行了這條SQL語句後,將產生許多輸出,這些都將存放在一個ResultSet類的對象rs中。它對應與圖13-5就是“結果集”。
8)
while(rs.next())
{
name=rs.getString(1);
sex=rs.getString(2);
age=rs.getString(3);
chinese=rs.getString(4);
maths=rs.getString(5);
output=name+" "+sex+" "+age+" "+chinese+" "+maths;
System.out.println(output);
}
根據圖13-5的指示,有了結果集,我們就可以使用next方法、getString方法獲取它們。其中next方法是指取下一個記錄(一個記錄由多個字段組成,在這個例子中共有5個字段:Name,Sex,Age,Chinese,Maths)。而getString則是從當前記錄中取出某個字段,它所帶的參數就是字段的序號。
一些提示:
剛返回的結果集ResultSet中,記錄指針是指向TOP,並未指向任何記錄。
在這個while循環中,首先將執行rs.next(),這樣,就指向了第一條記錄。(假如沒有第一條記錄,就會返回false,使得循環結束)。然後我們就可以使用getString(字段號)來獲取這條記錄中的字段。
當5個字段都取出來後,在程序中將它們組合在一起,然後將其打印出來。 接下來,就會再次執行rs.next()獲取下一條記錄,直到沒有記錄為止。
好了,到此你應該能夠理解這個程序了吧!
JDBC是一個十分強大、有用的工具,而且也內容很多,足以成冊,因此本書無法面面俱到說明,有愛好的話可以再去看一些Java的高級參考書。
自測練習
1) 使用JDBC,可分為四個步驟,請按順序將它們排列出來:
________、________、________、________。
a.釋放對象 b.語句處理 c.結果處理 d.創建連接
2) 在使用JDBC中,可能會用到許多方法,請將下列方法按照調用順序排列出來:
________、________、________、________。
a.getString() b.executeQuery()
c.getConnection() d.createStatement()
3) 在JDBC中,使用________說明JDBC數據源。
a.數據庫名 b.數據源名 c.JDBC URL d.JDBC對象名
4) 在jdbc:odbc:student中,odbc是___________。
a.協議名 b.驅動程序名 c.子協議名 d.橋接器
5) 在示例程序testjdbc.java中,我們選擇出了所有的五個部分,假如我不想選擇出Chinese、Maths字段,你認為程序應該做些什麼修改?
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
6) SQL語句SELECT的語法格式是:
SELECT 字段名 FROM 表名 WHERE 條件語名
其中WHERE子句可以使SELECT只選擇符合WHERE中說明的條件的記錄。
請您編寫一個程序,輸出年紀為9歲的所有學生的Name、Sex、Chinese。
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
練習答案
1)d b c a
2)c d b a
3)c 在JDBC中,我們應該使用JDBC URL來說明數據源;
4)c 在這裡odbc是子協議名;
5)共需要修改3處:
將
ResultSet rs=sta.executeQuery
("SELECT Name,Sex,Age,Chinese,Maths FROM student");
System.out.println("Name Sex Age Chinese Maths");
改為:
ResultSet rs=sta.executeQuery
("SELECT Name,Sex,Age FROM student");
System.out.println("Name Sex Age");
將以下兩行刪掉:
chinese=rs.getString(4);
maths=rs.getString(5);
將output=name+" "+sex+" "+age+" "+chinese+" "+maths;
改為:output=name+" "+sex+" "+age;
7) 以下是一個程序實例:
源程序:lianxi1301.java
import java.sql.*;
public class lianxi1301
{
public static void main(String args[]) throws SQLException
{
try
{
String name,sex,chinese,output;
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection
("jdbc:odbc:student");
Statement sta=con.createStatement();
ResultSet rs=sta.executeQuery("SELECT
Name,Sex,Chinese FROM student Where Age=9");
System.out.println("Name Sex Chinese");
while(rs.next())
{
name=rs.getString(1);
sex=rs.getString(2);
chinese=rs.getString(3);
output=name+" "+sex+" "+chinese;
System.out.println(output);
}
} catch(java.lang.Exception ex) {
ex.printStackTrace();
}
}
}
程序的輸出如下圖所示:
圖13-13 lianxi1301的輸出
13.3 編寫多線程Java程序
傳授新知
在今天,是否支持編寫多線程的程序,已經成為一個程序設計語言的重要方面。那麼什麼是多線程,什麼又是線程呢?
要搞懂什麼是線程,其實就是要能夠清楚地區分三個概念:“程序”、“進程”、“線程”。程序嘛,大家可能夠很輕易理解。比如說,“寫字板”、“Word”,還有我們前面寫過的程序。那麼什麼是“進程”呢?
在許多計算機理論書籍中的定義十分拗口,對於您來說,只需記住:“進程就是一個運行著的程序”。看到這裡,可能有人會說:“唉,說了半天,進程就是程序呀!一回事嘛!很簡單,我懂了”。很遺憾,我得告訴你,這樣的理解是錯誤的。請看下圖:
圖13-14 進程示意圖
我們啟動了兩次notepad,就出現了兩個記事本!而且,我們還可以在這兩個記事本上寫上不同的內容!
它們不是一個程序?它們可都是notepad呀,我們把這兩個運行中的程序稱為“進程”,不同的進程是完全不相關的。它們都有各地的內存塊,用來存放運行時的臨時數據(例如,在兩個記事本上的寫的字都是存放在各自進程的內存塊中的)。
不知大家是否有過使用DOS操作系統的經驗,在DOS中一次只答應執行一個程序,用剛學會的術語來說,就是只有一個進程。這種操作系統被稱為“單任務系統”。
而在Windows中,我們可以答應執行多個程序,也可以將一個程序執行多次,這種操作系統就是“多任務系統”。操作系統負責協調這些運行著的程序---進程。
正如下圖所示,在這種情況下,存在著互相不干攏的多個進程:
圖13-15進程示意圖
每一個圈代表一個進程,圓圈是這個進程的地址空間、寄存器……,中間的長方形是進程中的程序段,而黑色箭頭則是程序計數器。
這個進程獲得執行權時(CPU時間分片,輪到它),就從程序計數器開始執行。在程序時所有的數據都存放在自己的地址空間裡(圓圈中)。
一些提示:
操作系統其中一個職責就是治理進程,為它們分配不同的存儲區域,假如讓這些“圓圈”相交,程序就會崩潰,Windows就藍屏了!!
這個機制看起來十分的合理,不過碰到有些問題時,就會感覺到它並不是那麼盡善盡美了。例如,我們有一個程序用來完成從遠方接收信息,然後將其打印出來!
這個程序在工作時發現有這樣的問題:當打印機忙的時候,這個程序無法打印,它就被“阻塞”了,一直在等待打印機空閒。在這個等待的時間裡,也無法接收信息了。
這時,我們就可以使用“線程”來解決。
圖13-16 線程示意圖
上圖中,圓圈仍然代表一個進程,但我們在這個進程中有兩個不同的代碼段,它們共享進程的地址空間、寄存器……,一個負責接收遠方的信息,另一個負責打印出來!
這樣,假如碰到打印機忙的時候,也只會將負責打印的那個部分“阻塞”,接收信息還是照常運行的。
這兩個不同的代碼段,就是線程,也稱之為輕量進程。我們發現在一個程序中使用多個線程將會使我們的程序更Power!
注重:
線程的實現是要操作系統支持的,要寫多線程的程序則需要編程語言的支持。Window操作系統是支持多線程的。因此我們只要使用能夠支持多線程的編程語言就可以寫多線程的程序。Java就是一種能夠支持多線程編程的程序語言。
多線程的程序設計是比較高級的內容,而本書是一本Java的入門書籍,因此我們只對其做一個簡單的介紹,使得大家通過本書的學習具備自學編寫多線程的Java程序的能力。
在Java語言中,可以采用兩種方法來實現多線程:
1.繼續Thread類
也就是說,創建一個Thread類的子類,如:
public class testThread extends Thread
由於這種方法顯得較為死板,所以我們一般很少使用這種方法。
2.使用Runnable接口
正如前面所說的,通常我們希望我們的類擴展其它類,而在Java中又不支持多重繼續(也就是同時繼續A、B兩個類)。我們必須解決這個問題:
一些提示:
例如,我們想在一個Java的Applet程序中使用多線程,但我們要編寫Applet
程序就必須繼續java的Applet或Japplet類,那我們就無法再繼續Thread類。因 為Java是不答應一個類同時繼續兩個類的。
Java提供了一個Runnable接口來實現這個問題。
1) 使用Runnable接口,首先在類的定義後面加上implements Runnable,如:
public class testThread extends Applet implements Runnable
2) 然後,再重寫定義一個run()方法,定義線程要做的事情。
實例說明
下面,我們就來編寫一個多線程的程序。首先創建兩個源程序:testThread.java和Threader.java。
源程序:testThread.java
import java.awt.*;
import java.applet.*;
import Threader;
public class testThread extends Applet implements Runnable
{
Threader theRacers[];
static int racerCount = 3;
Thread theThreads[];
Thread thisThread;
static boolean inApplet=true;
int numberofThreadsAtStart;
public void init()
{
numberofThreadsAtStart = Thread.activeCount();
setLayout(new GridLayout(racerCount,1));
theRacers = new Threader [racerCount];
theThreads = new Thread[racerCount];
for (int x=0;xnumberofThreadsAtStart+2)
{
try
{
thisThread.sleep(100);
}
catch (InterruptedException e)
{
System.out.println("thisThread was interrupted");
}
}
if (inApplet)
{
stop();
destroy();
}
else
System.exit(0);
}
public static void main (String argv[])
{
inApplet=false;
if (argv.length>0)
racerCount = Integer.parseInt(argv[0]);
Frame theFrame = new Frame("The Great Thread Race");
testThread theRace = new testThread();
theFrame.setSize(400,200);
theFrame.add ("Center",theRace);
theFrame.show();
theRace.init();
theFrame.pack();
theRace.start();
}
}
源程序:Threader.java
import java.awt.*;
public class Threader extends Canvas implements Runnable
{
int myPosition =0;
String myName;
int numberofSteps=600;
boolean keepRunning = true;
public Threader (String inName)
{
myName=new String (inName);
}
public synchronized void paint(Graphics g)
{
g.setColor (Color.black);
g.drawLine 0,getSize().height/2,
getSize().width,getSize().height/2);
g.setColor (Color.yellow);
g.fillOval((myPosition*getSize().width/numberofSteps),0,
15,getSize().height);
}
public void stop()
{
keepRunning = false;
}
public void run()
{
while ((myPosition
輸入完成以後,執行以下命令編譯:
javac testThread.java Threader.java
然後,我們再執行命令:
java testThread
程序的輸出如下圖所示:
圖13-17 程序testThread.java的輸出
你看,三個人在賽跑!看誰快,他們太接近了,誰是第一名呢?所以我們來看看在DOS窗口的輸出:
圖13-18 MS-DOS下的輸出
我們看到,程序的輸出給出了公正的判決。第一次賽跑(第一次運行這個程序),冠軍是第三道(Racer #2),第一道(Racer #0)屈居第二,第二道(Racer #1)是最後一名。第二次賽跑時,第二道反而成為了第一名。我們再運行幾次程序,就會發現每一次的成績都可能不同。你來可以使用以下命令來讓更多的“人”參加賽跑:
java testThread 5
傳授新知
這個程序太龐大了,也許會把大家嚇倒!我們來看一下這個程序結構。這個示例由兩個文件組成:testThread.java和Threader.java。
其中,Threader中定義了競賽者對象,我們先一起來看一下這個類:
1)
public Threader (String inName)
{
myName=new String (inName);
}
這是類Threader中構造器方法,它為Threader設置對象名。
2)
public synchronized void paint(Graphics g)
{
g.setColor (Color.black);
g.drawLine (0,getSize().height/2,getSize().width,
getSize().height/2);
g.setColor (Color.yellow);
g.fillOval((myPosition*getSize().width/numberofSteps),0,
15,getSize().height);
}
我們為Threader類定義了一個paint方法,我們看一下這個方法做了些什麼:
前兩條語句用來畫出這個競賽者(Threader)的賽道,一條黑色的賽道!這條賽道用一根線來表示:它從(0,getSize().height/2)到(getSize().width,getSize().height/2)。也就是一個從最左邊到最右邊的,位於中心的黑線。
然後,我們畫出這個競賽者,它用一個黃色的橢圓來表示。這個橢圓寬15,高為整個格子,位置由變量myPosition決定。
一些提示:
我們將myPosition的初值設置為0.將整個賽道分成numberofSteps步,即600步。MyPosition每加1,就走過一步。
2)
public void run() {
while ((myPosition
這是一個while循環,當myposition小於numberofSteps,就將myposition加1(往前走一步),然後重畫(這樣這一步就會顯示出來)。
假如myposition=numberofSteps時,意味著什麼?對,意味著走完了賽程,因此,打印出完成信息。
大家看到,我們重畫後,我們使用了這樣一條語句:
Thread.currentThread().sleep(10);
這是讓當前線程進入休眠狀態一小會(10個時間周期)。這是為什麼呢?要理解這個問題,我們需要學習一下CPU是如何治理這些線程的。線程與進程一樣有三種狀態:
§ 運行態:線程正在運行;
§ 就緒態:線程一切就緒,可以運行,正在等待CPU運行;
§ 阻塞態:線程未預備就緒,正在等待某個條件。
其間的關系,如下圖所示:
圖13-19 線程狀態轉換圖
由於在單處理器的系統中,一個時間內CPU只能運行一個線程。所以假如我們在每個競賽者跑一步時,就讓它休眠(進入阻塞態,等待休眠時間到),這樣就不會讓一個線程一直占用CPU,以免不公平嘛!
由於我們讓線程的休眠時間比較短,所以一會就回到了,這時線程就進入就緒態,等待CPU有時間的時候運行。CPU呢一有時間,就從就緒的線程中選擇一個來運行。
大家看到這裡,可能早已昏頭轉向了,下面我們舉一個生活中的例子來模擬一下這個情況:有三位職員(對應程序中的三個競賽者、即三個線程)要向經理(對應運行線程的CPU)匯報工作。但這個經理采用了一個十分公平的方法(當然在現實生活中是不可能的)來接受三個職員的匯報。也就是每一個職員一次只說一句話(每一個競賽者跑一步),然後就呆一邊休息一下(休眠10個時間周期),然後從另兩個職員中任選一個來說(選擇就緒的線程)。也是說一句話,就讓他休息。這樣周而復始,直到他們都匯報完畢。
情況如下圖所示:
圖13-20 競賽者線程示意圖
一些提示:
以上所述的關於線程的描述,是基於支持多線程的系統。假如不支持多線程的話,這是不成立的。在不支持線程的系統中,則將線程改成進程就行了。
接下來,我們看一下testThread.java程序,這才是我們執行時的主體部分:
3)
public static void main (String argv[])
{
inApplet=false;
if (argv.length>0)
racerCount = Integer.parseInt(argv[0]);
Frame theFrame = new Frame("The Great Thread Race");
testThread theRace = new testThread();
theFrame.setSize(400,200);
theFrame.add ("Center",theRace);
theFrame.show();
theRace.init();
theFrame.pack();
theRace.start();
}
這個程序執行時,首先將運行這個main方法。然後通過判定argv.length是否大於0,假如大於0,就將執行時所帶的參數賦給racerCount(參賽人數),否則就為3(在程序的開始處有定義。
然後創建一個Frame,然後調用init()和start()方法。
一些提示:
注重,在這裡我們創建一個testThread類的對象theRace,通過它來調用。
4)
for (int x=0;x
在theRace的init()方法中,最主要的部分就是這個for循環。通過這個循環我們創建了racerCount個Threader(競賽者),並將它們命名為“Racer #x”,然後設置它的高度(整個Frame的高度/競賽者數)。最後調用Frame的add方法將它們顯示出來。
然後為每一個競賽者(Threader)創建一個線程Thread。也就是說為每一個競賽者創建一個線程,用這個線程來控制它。
一切就緒之後,我們就要開始“賽跑”了。
5)
public void start()
{
for (int x=0;x
接下來,我們調用了theRace的start()方法,我們看到在這個方法中,我將啟動了“控制”所有參賽者的線程。(調用Thread的start方法)
多線程編程是相當復雜的,本書中僅給大家整理一下思路,做一些簡要的介紹,大家可以閱讀一些更高級的Java書籍,來更深入地學習。
自測練習
1) 以下關於進程的說法中,____________是正確的。
a.就是程序 b.一個運行中的程序 c.由線程組成
2) 以下關於線程的說法中,____________是正確的。
a.是一種輕量線進程 b.一個進程中只能有一個線程
c.每個線程都有自己的地址空間
3) Java語言中有兩種方法實現多線程,它們是__________、 ____________。
a.繼續Thread類 b.繼續Threader類
c.使用Runnable接口 d.使用Thread接口
4) 最常使用的多線程實現方法是__________。
a.繼續Thread類 b.繼續Threader類
c.使用Runnable接口 d.使用Thread接口
請說明理由:
____________________________________________________________________
____________________________________________________________________
5) ___________不屬於線程的狀態:
a.運行態 b.完成態 c.就緒態 d.阻塞態
6) 請描述出使用Runnable接口實現多線程的步驟:
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
7) 假如我們使用java testThread a來執行這個程序的話,就會出錯,請問這是為什麼?請說明:
____________________________________________________________________
____________________________________________________________________
練習答案
1)b 並不是所有的進程都是由多個線程組成的。進程是程序的一個運行實例,它與程序是不等價的。
2)a 在支持線程的系統中,我們可以在一個進程中細分出多個線程,但並不是說每一個進程中都有多個線程。在一個進程中的線程是共享地址空間的。線程,也稱為輕量級進程。
3)a、c 有兩種方法,分別是繼續Thread類,以及使用Runnable接口。
4)c 最常用的方法是使用Runnable接口。
這是因為,我們經常需要繼續其它類,而Java又不支持多重繼續,所以使用繼續Thread類有很大的局限性。
5)b 當一個線程完成後,就釋放了,並不存在一個完成態。
6)第一步:在類的定義後加上implements Runnable,以使用Runnable接口;第二步:重寫定義一個run()方法,定義線程要做的事情。
7)將會出錯。這是因為我們希望參數是競賽者的人數,也就是需要整型數,而不是字符型。假如輸入字符型就會造成程序出錯。
13.4 JavaBeans
13.4.1 什麼是組件(部件)技術
傳授新知
通常,我們開發一個應用軟件時,都是事先經過需求分析、設計,然後開發出所需的軟件。在這個軟件系統中,各種功能、特性用固定的方法聯系在一起。但是,軟件的組成部分大多無法獨立地刪除、升級。
這樣造成的結果就是,不同的應用程序即使用同一種編程語言,在同一台機器上使用,也無法互相利用,它們之間就象是陌路人一樣。
另外,由於操作系統不同,使得在不同的操作系統上完成一個相同的工作,往往實現的方法也就不同。盡管,國際上有許多標准化組織制定了許多規范來減少各個操作系統的開發接口的不同,但是這也只能夠是一個權宜之計。
但是隨著應用的不斷擴展,經常需要讓那些基於不同設計的軟件交互,甚至一起工作。傳統的方法是通過一組系統服務API(Application Programe Interface,應用編程接口)來實現。其工作原理如下圖所示:
圖13-21 通過API來利用原來的程序功能
通常會提供API的,都是一些系統軟件,它們本身在設計時就充分考慮到讓其它用戶程序通過API來調用自身的“功能”模塊。而假如一個軟件本身在設計時並未考慮到API的話,就無法使用這種方法來實現,因此這種模式有極大的局限性。
矛盾與需求必將促進技術的發展。在20世紀90年代,出現了一種基於面向對象技術的軟件工程技術---組件技術。這是一個新一代軟件技術發展的裡程碑。
一些提示:
面向對象技術是出現在20世紀80年代,但它並未完全解決我們碰到的問題。因為經缺乏一種讓不同開發商提供的軟件對象在同一地址空間裡相互合作的機制。
這些有用的對象就象是“千軍萬馬”,但我們還缺少一個能夠“統帥三軍”的人物。而組件技術就是這個“統帥”。
組件技術的基本思想就是:創建和利用可復用的軟件組件來解決應用軟件的開發問題。組件是一種可復用的一小段軟件(可以是二進制的,而不是源代碼)。組件可以有多種多類,小到圖形界面上的一個按鈕,大到一個復雜的軟件。
只要開發者、應用商將它們的軟件作品組件化,那麼程序員們就可以在這些組件的基礎上,根據自己的需要,將不同語言、不同廠商的組件組合在一起,編制自己的應用程序。通過復用這些組件,就能夠使應用程序開發變得更加簡單、更加快速,而且成本更加低廉。
組件技術的出現,使得:
1) 大大提高開發速度:由於許多軟件模塊都是通過復用這些已用的軟件組件而成,因此將大大減少開發量,縮短開發周期。
2) 降低開發成本:開發量的減少,開發周期的縮短,都會節約大量的成本。
3) 增加應用軟件的靈活性:當軟件中的某個組件升級了,功能增強了!我們只需簡單地換掉這個組件,馬止就可以升級整個應用軟件。而且我們還可以根據自己需要將它們個性化,這大大提高了應用軟件的靈活性。
4) 降低維護費用:由於采用組件技術開發的應用軟件,可以通過局部修改達到優化、修改軟件的目的,而不必重新做全局修改,這將大大降低軟件的維護費用。
最後,我們通過一個通俗、簡單的例子,來說明使用組件技術開發應用軟件前後的情形,希望這個例子能夠幫助您理解組件技術的真谛。
我們要開發一個具有收音、放音、錄音三種功能的“三用機”。
一些提示:
這其實是一個無線電技術方面的開發,我們在此就把它做一個類比。
1) 使用組件技術之前,我們開發應用軟件就象:
我們做這個“三用機”時,我們放著現成的實現“收音”功能的集成電路、實現“放音”功能的集成電路、實現“錄音”功能的集成電路不用,非得推倒一切,重新設計。由於時間有限,他可能做出了這些功能,但是整體的設計就可能無法讓用戶接受,最後可能得到一個不好的產品。而且要維護、維修這個產品,將會受到極大的困難。
這樣做新產品開發的硬件工程師,肯定會受到各方面的批評吧!我想你也會覺得這種不可思義的工作方法是十分可笑的吧!
可是,很遺憾的是,我們的軟件開發工作卻一直使用這種“令人可笑”的方法來做的。但這也是因為軟件產業尚未成熟,還沒有整理出“收音集成電路”、“放音集成電路”、“錄音集成電路”這些可復用的模塊。只好使用這種“沒有辦法的辦法”來做這樣的工作。這種現狀,也就是“軟件危機”。
2) 使用組件技術之後,我們開發應用軟件就完成不一樣了:
我們做這個“三用機”時,我們認真設計,將實現“收音”、“放音”、“錄音”功能的集成電路組合在一起,然後認真設計用戶界面,外觀、其它功能。結果我們得到了一個十分新潮的“三用機”
我想,這種方法在硬件工程設計上早已不是什麼新技術了,正是這種不斷地站在巨人的基礎向前發展的經歷,使得我們的CPU越來越快(你現在知道,為什麼摩爾定律為什麼會實現了吧!)。
因此,想讓我們的軟件開發工作能夠象硬件開發一樣有效率的話,也得總結出各種有效的“組件”,以後應用軟件的開發變得象“搭積木”一樣簡單有效。
13.4.2 主流組件技術
傳授新知
但是真正有效的軟件組件,並不能夠隨意構造。因此,軟件界就開始了一場組件軟件的體系結構和組件間的接口方式的研究。並且許多軟件廠商、組織制定出了許多這方面的方案與規范,比較有代表性的有三種:CORBA、COM/DCOM/OLE/ActiveX、JavaBeans。
1.CORBA
歷史最悠久的是CORBA組件技術。它是由OMG(對象治理組織)制定的。OMG組織有許多大名鼎鼎的IT公司:HP、3COM、SUN、CANON、PHILIPS等。它使得異構系統中的部件能夠很輕易地通信,就像是在本地進行通信一樣。
2.COM/DCOM/OLE/ActiveX
最有市場競爭力的是Microsoft公司推出的COM/DOCM/OLE/ActiveX組件技術。它性能優越,但它僅局限於Windows平台上使用。
3.JavaBeans
由於組件技術規范具有不可小視的重要性,因此Sun公司為了與Microsoft競爭,在不久前發布了基於Java的組件技術標准:JavaBeans。它的目標是實現一種與平台無關的組件技術。不過,由於Java的地位不斷的提高,使得對手Microsoft公司也對其提供了支持。
小知識:
第一個JavaBeans規格書是在1996年9月公布。這個規格書是多家公司合作的結果。它包括Apple、Borland、IBM、JustSystem、Microsoft、Netscape、RogueWave、SunSoft和Symantec等等。
到現在,又有許多新的公司公布對JavaBeans提供支持,這些公司包括:Corel、EnterpriseSoft、Gemstone、Jscape、K&A Software、KL Group、Lotus Development、NOVELL、ProtoView、Development、Rogue Wave和Stingray Software等。
關於JavaBeans的可多的內容,我們會在下一小節中講述。
那麼在這些組件技術,哪一個能夠“浪沙淘盡,誰是英雄”呢?從現在的發展趨勢來看,可能答案是“群雄逐鹿”。這是因為這三種組件技術各有優缺點,並不存在一種技術有明顯的優勢。而且它們之間還在不斷地融合,互相提供接口橋,因此,這些組件技術均可能占據一定的市場。
13.4.3 組件技術:JavaBeans
傳授新知
在組件技術JavaBeans中,每一個組件就是一個Bean,Bean可以結合起來,開發出應用程序。一個組件Bean就是使用Java語言編寫的一個組件。
JavaBeans是一個十分完整的組件模型,它能夠支持標准組件技術中的各種特性:屬性(properties)、事件(events)、方法(method)和持續性(persistence)。
1) 組件的屬性:
組件要能夠被使用,那麼就必須有一組屬性來定義他的狀態。例如,一個圖形組件可以有屬性:前景色、背景色。而一個計算儲蓄的組件的屬性可能就有本金、利率等。
大家想一下,它象不象一個對象的屬性。
其實組件就是由一個或多個對象組件的,是一個更大些的軟件組成部分。
2) 組件的方法:
與一個對象一樣,組件提供了一系列的方法(也可以理解為函數),其它部分或其它軟件通過調用這些方法來使用組件,或修改組件的狀態。
3) 組件的事件:
由於組件技術是基於面向對象技術實現的,所以一樣的,也是一種消息驅動機制,組件間是通過消息進行通訊的。這些消息就是事件。
例如,有一個“按鈕”組件,當我們單擊這個“按鈕”時就會產生一個事件。
設計一個JavaBeans的步驟是:
1) 指定Beans的屬性;
2) 指定Beans所產生或響應的事件;
3) 定義Beans應公開給其它Bean的屬性、方法和事件。
JavaBeans是一個比較復雜的課題,更多的內容你可以參考《Using JavaBeans》一書。
自測練習
1) 組件技術的基礎是_______________。
a.JavaBeans技術 b.傳統的軟件開發方法
c.面向對象技術
2) 最早的組件技術規范是____________。
a.JavaBeans b.CORBA c.COM/DCOM
3) 與平台相關的組件技術規范是____________。
a.JavaBeans b.CORBA c.COM/DCOM
4) OMG組織制訂的組件技術規范是_________。
a.JavaBeans b.CORBA c.COM/DCOM
5) 以下關於組件的說法,正確的是__________。
a. 組件就是一個可復用的“對象”
b.組件技術是一個軟件開發技術的裡程碑
c.使用組件技術開發,會耗費更高的成本
練習答案
1)c 組件技術的基礎是面向對象技術。JavaBeans是一種組件技術,它是為了解決傳統的軟件開發方法提出來的一種改進。
2)b 最早的組件技術規范是OMG組織開發的CORBA技術規范。
3)c COM/DCOM組件規范是Microsoft公司制定的,它只能夠在Windows平台上使用,它是一種平台相關的技術。
4)b CORBA規范是OMG組織開發的。
5)b 組件技術是軟件開發技術的一個裡程碑。它比對象的粒度要大,它可以由多個對象組成。使用它能夠更加高效地進行組件開發。
13.5 Java的學習資源
傳授新知
善用Java的學習資源,能夠給你學習Java插上騰飛的雙翼!
13.5.1 最好的學習資源JDKDOC
學習Java語言,最好的資源就是JDKDOC(Java Development Kit Documentation),你可以在sun公司網站上下載。下圖就是JDKDOC文檔的主頁:
圖13-22 JDKDOC
13.5.2 WEB站點
Java語言更新迅速,要把握最新的內容,最好的方法就是通過Java相關的WEB站點來學習。下面就是一些聞名在Java站點,不過可惜的是大多都是英文站點。
1. Java的老家:
鏈接:www.javasoft.com java.sun.com
簡介:這是查找各種Java官方資源的好地方,我們可以在這裡下載到最新的JDK、Java工具、以及JDKDOC。
2. Earthweb的開發網站:
鏈接:www.gamelan.com
簡介:它是所有Java資源站點的始祖。
3. Java Focus網站:
鏈接:www.miningco.com
簡介:在這裡你能夠找到一大批關於Java的非凡信息。
4. Java Inside網站:
鏈接:www.inside-java.com
簡介:這時Java程序員的一個很極好的信息源。這裡我們可以找到許多關於Java語言的論文,以及許多Java的最新動態。
5. Java小應用程序評級服務網站:
鏈接:www.jars.com
簡介:這是一個十分有趣的網站,它提供了一個Java小應用程序的排名。
6. Java開發者雜志
鏈接:www.javadevelopersjournal.com/java/
簡介:在這裡有免費的Java講座,有免費的軟件,可惜雜志並不是免費的,你只能免費試讀3期。
7. Java Lobby
鏈接:www.javalobby.org
簡介:這是一個由Java開發人員組成的群體。
8. Java世界
鏈接:www.javaworld.com
簡介:這是一個關於Java的在線月刊。它是IDC公司面向Java一族的經典雜志。
9. 微軟公司的Java主頁:
鏈接:www.microsoft.com/java
簡介:假如你想了解在Microsoft window平台上的Java的信息,你可以到這裡看一看。注重它的Java可是不“純”的喲。
10. Java團隊
鏈接:www.teamjava.com
簡介:這裡的目的是通過提供關於Java的工作機會、新聞、教育材料及其它有用的Java資源,以向Java愛好者服務。
13.5.3 新聞組
以下是最經典的Java新聞組列表:
1) comp.lang.java:Java語言和編譯;
2) comp.lang.java.advocacy:Java支持者的論點;
3) comp.lang.java.announce:Java產品及其它服務公報;
4) comp.lang.java.beans:JavaBeans討論與編程;
5) comp.lang.java.databases:Java數據庫編程;
6) comp.lang.java.gui:圖形用戶界面提示及幫助;
7) comp.lang.java.help:關於Java編程語言的一般性幫助;
8) comp.lang.java.machine:Java虛擬機討論;
9) comp.lang.java.programmer:Java程序員幫助;
10)comp.lang.java.security:Java安全性討論;
11)comp.lang.java.softwaretools:Java工具討論組;
好,到此相信各位讀者已經走進了Java的殿堂,可謂是“師傅”(筆者冒昧當一回師傅)帶進門,修行靠個人。希望這些廣泛的資源能夠幫助你遨游Java世界。