多線程
在單獨線程中運行任務的簡單過程:
1. 定義一個實現了Runnable介面的類,任務代碼寫到run方法中
2. 建立一個該類的執行個體
3. 將這個執行個體傳遞給Thread類的構造體,執行個體化一個Thread對象
4. 通過Thread類的start方法開啟線程
中斷線程:線程會在它的run方法返回時終止
儘管現在已經沒有強制終止線程的方法,但是可以用interrupt方法請求終止一個線程;當interrupt方法在一個線程對象上被調用時,該線程的中斷狀態位將被置為true;為查明一個線程的中斷狀態,需要首先調用Thread類的靜態方法currentThread擷取當前線程的對象,然後調用它的isInterrupt方法進行檢測;但是,如果一個線程已經被阻塞,對它調用interrupt方法被拋出InterruptedException
請求被中斷的線程並不一定需要被終止;檢測到中斷狀態位被置為true之後應該根據實際情況進行處理
對於InterruptedException更好的處理方法是將它拋出給更高層次的代碼處理,而不是將它抑制在低層次
線程狀態:線程可以有如下4種狀態
1. New:通過new建立線程對象後,線程開始運行之前,線程進入“新生”態,可以進行一些簿記工作
2. Runnable:一旦調用了start方法,線程就進入了“可運行”態,開始執行run方法中的代碼,但實際是否在運行取決於作業系統的線程調度
3. Blocked:當發生如下任何一種情況時,線程進入“阻塞”態:
1) 線程通過調用sleep方法進入睡眠
2) 線程調用了一個在I/O上被阻塞的操作
3) 線程試圖得到一個鎖,而該鎖正被其它線程持有
4) 線程在等待某個觸發條件(參見Synchronized關鍵字)
5) (已廢棄)suspend方法被調用
通過如下途徑中的一種,線程可從阻塞態回到可執行態:
1) 處於睡眠的線程已經經過了指定的睡眠時間
2) 線程正在等待的I/O操作已經完成
3) 其它線程釋放了本線程正在等待的鎖(或因為逾時線程自動解除阻塞)
4) 其它線程發出訊號表明本線程正在等待的觸發條件已經變化(或因為逾時線程自動解除阻塞)
5) (已廢棄,針對suspend)線程被掛起後,它的resume方法被調用
4. Dead:以下情況會導致線程“死亡”:
1) run方法執行結束,正常退出
2) 因為一個未捕獲的異常導致run方法終止
3) (已廢棄)調用stop方法殺死線程
4) 調用join方法可以使線程等待直到死亡
調用線程的isAlive方法檢測線程是否存活:可運行態和阻塞態返回true,新生態和死亡態返回false
線程屬性:線程優先順序:每個線程都有一個優先順序,預設情況下,線程會繼承它父線程的優先順序;setPriority方法可以設定線程優先順序,最小為MIN_PRIORITY(1),最大為MAX_PRIORITY(10),另外常量還有NORM_PRIORITY(5);作業系統的線程調度會優先處理高優先順序的線程;靜態方法yield會使當前線程進入讓步狀態,讓優先順序不低於它的其它線程優先被調度
守護線程:調用t.setDaemon(true)會使線程t變成守護線程;守護線程唯一的作用是為其它線程提供服務;當只剩下守護線程時,程式終止
線程組:線程組允許同時對一組線程進行操作
ThreadGroup類的構造體中的字串參數用來標識該組,必須是唯一的;建立新線程後為它指定線程組就可以將它添加到線程組中;ThreadGroup類的activeCount可以檢查該線程組中可運行態的線程個數;對線程組的操作可以操作它裡面的所有線程
未捕獲異常處理器:線程的run方法不能拋出異常;線程因為未捕獲的異常而死亡前,異常會被自動傳遞給未捕獲異常處理器;未捕獲異常處理器必須是實現了Thread.UncaughtExceptionHandler介面的類,並通過setUncaughtExceptionHandler方法設定
同步:Java中有兩種機制來保護代碼塊不受並行訪問的幹擾:
1. ReentrantLock類:結構形如:
myLock.lock();
try {
…
}
finally {
myLock.unlock();
}
其中myLock是ReentrantLock類的執行個體,結構中lock方法和unlock方法之間的代碼會被加上同步鎖;使用try/finally是為了即便是拋出異常也一定要執行unlock,以避免死結,沒異常的情況下可不用
鎖是可重新進入的(線程可以重複擷取它已經擁有的鎖);鎖對象維護一個持有計數來追蹤對lock方法的嵌套調用(lock加1,unlock減1),當持有計數為0時,鎖被釋放
一個鎖對象可以用一個或多個相關聯的條件(Condition)對象;可以通過ReentrantLock類的newCondition方法擷取一個新的條件對象;條件對象調用await方法可以使持有鎖的這個線程進入阻塞態並且放棄鎖,它維持阻塞直到另一個線程調用同一個條件對象的signalAll方法;signalAll方法將解除所有等待此條件的線程的阻塞態,這些線程將再次競爭鎖,一旦獲得鎖,該線程將從await之後繼續執行;另外還有signal方法可以隨機地解除等待這個條件的線程中的一個的阻塞
一旦一個線程因為await進入阻塞態,它就必須等待別的線程調用signalAll或signal方法來解除它的阻塞,否則它將永遠不會再運行了(死結);如果最後一個活躍線程在解除別的線程的阻塞前就調用了await方法,則整個程式將被掛起
總結:
1) 鎖用來保護程式碼片段,任何時候只允許一個線程執行被保護的代碼
2) 鎖可以管理試圖進入被保護程式碼片段的線程
3) 鎖可以擁有一個或多個相關的條件對象
4) 每個條件對象管理那些已進入被保護程式碼片段但不能啟動並執行線程
2. Synchronized關鍵字:隱式的鎖;線程通過兩種方法獲得Synchronized鎖:調用一個同步方法或進入一個同步塊
同步方法:用Synchronized關鍵字修飾一個方法,那麼對象的鎖將保護整個方法;隱式對象鎖只有一個關聯條件,Object類的wait方法會把線程加到等待集中,notify和notifyAll方法可以解除等待線程的阻塞態
同步塊:形如:
Synchronized(obj) {
…
}
其中obj可以是一個無意義的Object對象,也可以當前方法需要操作的對象(如this)
volatile關鍵字修飾的域將在被同步訪問時提供一個“免鎖”機制,即編譯器和虛擬機器都知道該域可能會被或正在被另一個線程並發更新(需同步檢查)
如下條件有其一,則對一個域的並行訪問是安全執行緒的:
1. 該域是volatile的
2. 該域是final的,並且已經被賦值
3. 對域的訪問有鎖保護
公平鎖:建立ReentrantLock時,將它的構造體參數設為true,則建立了一個公平鎖對象,它會優待那些等待了最長時間的線程,但是會大大影響效能;預設鎖都不是公平的
ReentrantLock類提供了tryLock方法來嘗試加鎖,成功則返回true,否則返回false,以避免lock方法不時帶來的線程阻塞;這個方法會搶奪任何可用的鎖,即便當前的ReentrantLock對象被設定為公平的;tryLock和await的重載都有設定逾時參數的形式,逾時後等待線程的阻塞將被解除
讀/寫鎖ReentrantReadWriteLock適用於多個線程都從某一資料結構中讀取資料而很少線程對其進行修改的時候;它的使用必要步驟:
1. 建立ReentrantReadWriteLock對象
2. 抽取讀鎖對象(readLock方法)和寫鎖(writeLock方法)對象
3. 對程式碼片段加讀鎖或寫鎖,類似ReentrantLock的結構
其中讀鎖排斥所有寫操作,寫鎖排斥所有其它的讀操作和寫操作
阻塞隊列:在多線程進行合作時,工作者線程可以定期將中間結果存放到阻塞隊列中,其它線程可以在工作的時候可以把中間結果取出並修改;試圖向一個滿的阻塞隊列中添加新元素,或從空的阻塞隊列中移除元素時,將導致線程阻塞
阻塞隊列自動進行同步,安全執行緒
java.util.concurrent包中提供了四種阻塞隊列,它們都是實現了BlockingQueue<E>介面的泛型類:
1. LinkedBlockingQueue<E>,預設下容量沒有上限,但可以指定
2. ArrayBlockingQueue<E>,必須指定容量,並可以是否需要公平性
3. PriorityBlockingQueue<E>,帶優先順序的阻塞隊列,進出按優先順序順序,容量無上限
4. DelayQueue<E extendsDelayed>,必須包含實現了Delayed介面的對象
安全執行緒的集合:ConcurrentLinkedQueue,ConcurrentHashMap
CopyOnWriteArrayList,CopyOnWriteArraySet
Vector,HashTable
Callable和Future:Callable介面和Runnable類似,但它有傳回值,它是泛型化的介面(Callabl<V>,其中V就是它的唯一方法call的傳回值類型)
Future介面也是泛型化的介面;它具有兩個get方法:無參的get方法會被阻塞直至計算完成,有參的get方法能設定逾時時間;此外還有一些檢測狀態的方法
實際應用中,只需要實現Callable介面的類,然後將該類的對象傳遞給FutureTask封裝器類的構造體,就能建立一個結合了Runnable和Future介面功能的新對象;FutureTask類的執行個體可能替換Thread中實現了Runnable介面的對象;另外,FutureTask類也提供了Future介面中的方法
執行器:如果程式需要大量生存期限很短的線程,則應該使用線程池,以減少建立新線程的開銷;線程池中包含大量準備啟動並執行空閑線程,當Runnable對象傳遞給線程池時,線程池中的一個線程自動調用run方法;當run方法退出時,該線程回到線程池中準備下一次使用
另一個使用線程池的原因是減少並發線程的數量,通過使用一個線程數“固定”的線程池來實現
執行器Executor類有大量用來構建線程池的靜態Factory 方法:
1. newCachedThreadPool:構建一個線程池,對於每個任務,如果有空閑線程可用則立即執行它,否則建立一個新線程;空閑線程將被保留60秒
2. newFixedThreadPool:建立一個大小固定的線程池;若任務數大於空閑線程數,則得不到執行的任務將被置於隊列中等待其他任務完成
3. newSingleThreadExecutor:建立一個容量為1的線程池,所有任務串列執行
上述三種方法返回一個實現了ExecutorService介面的ThreadPoolExecutor類的對象;該對象提供3個重載的submit方法來將Runnable或Callable對象提交給ExecutorService,並返回Future對象
使用完線程池之後需要調用shutdown方法,該方法啟動池的關閉序列,使池不再接收新的任務,並在現有的線程都死亡後關閉池;還可以調用shutdownNow方法,池會取消還沒開始的任務並試圖中斷池中可運行態的線程
4. newScheduledThreadPool
5. newSingleScheduledThreadExecutor
上述兩個方法返回一個實現了ScheduledExecutorService介面的對象;該介面具有為預訂或重複執行任務而設計的方法,可以預定Runnable或Callable在初始延遲後只運行一次,也可以預定一個Runnable對象周期性運行;詳見Java API
控制線程組:實現了ExecutorService介面的線程池對象的invokeAny和invokeAll方法可以處理一組Callable介面的任務,詳見Java API
同步器:同步器是一些為線程之間的共用集結點提供“預置功能”的類;集結點指線程調用的一個聚集地,每個線程都將結果匯聚於此,形成完整的結果;線程集如果達到可處理的集合點中的一種,就可以直接重用合適的類,而不是手動維護一個鎖集合
1. 障柵:CyclicBarrier類實現了一個稱為“障柵”的集合點:當大量線程運行在一次計算的不同部分時,所有部分都處理完之後需要被整合;障柵存在於最後的整合之前,每個線程完成它的任務後,運行到障柵處等待,所有線程都到達障柵處時,障柵被撤銷,線程可以繼續運行;步驟:
1) 建立一個障柵類CyclicBarrier的對象,在構造體參數中設定線程數
2) 每個線程任務(Runnable的run方法中)的最後,障柵對象調用它的await方法
2. 倒計時門栓:CountDownLatch類讓線程集等待直至計數器減少(coutDown方法)為0,用於多個線程需要等待直至指定數量的結果可用的情形
3. 交換器:Exchanger類允許兩個線程在要交換的對象準備好時交換對象,可用於當兩個線程工作在同一個資料結構的兩個執行個體上的時候
4. 同步隊列:同步隊列是一種生產者和消費者線程配對的機制;當一個線程調用SynchronousQueue的put方法來傳遞某個執行個體時,它會被阻塞直到另一個線程調用take方法取走這個執行個體
5. 訊號量:訊號量Semaphore類管理大量的許可證,可以通過acquire方法申請,也可以通過release方法釋放(但不需要申請它的線程來釋放);擁有許可證的線程代碼才能被執行,否則所在的行程被阻塞直至獲得許可證;許可證的數量就是限制通過的線程數
總的來說,五種同步器的類就是線上程的任務代碼中添加特定的等待條件(集合點),若條件滿足,則繼續執行,否則線程被阻塞直至條件被滿足;每個條件同時提供了更新目前狀態的方法(使線程匯聚到集合點的途徑)
網路
通訊端:socket,網路軟體中的一個抽象概念,負責使程式內部和外部之間的通訊;Socket類通過傳入的遠程地址和連接埠號碼來建立對象
伺服器通訊端:ServerSocket,通過傳入構造體的連接埠號碼來建立對象,負責監聽該連接埠的網路資料
ServerSocket類accept方法可以阻塞調用這個方法的線程,直到有用戶端的串連到這個連接埠,此時該方法返回一個表示該用戶端的Socket對象
輸入輸出資料流:Socket類的getInputStream方法和getOutputStream方法可以得到與從遠程用戶端讀取資料的輸入資料流和向遠程用戶端寫入資料的輸出資料流;寫入資料時,print類型的方法比write方法更有效;與流使用結束後需要close一樣,Socket使用完後也應該close
為多個用戶端服務:accept方法每返回一個新串連的用戶端的Socket對象時,新開啟一個線程來專門處理這個Socket對象的資料讀取與寫入
發送Email:構造Socket對象時的參數分別是郵箱伺服器的地址(String)和連接埠號碼25(int,25是SMTP協議的系統固定連接埠號碼);向輸出資料流中寫入郵件資料時應按照SMTP規範;JavaMail API中提供了一個靜態方法Transport.send(String)可以用於發送Email
URI與URL:URI(統一資源識別項)是個純粹的文法結構,用於指定標識Web資源的字串的各個不同的部分;URL(統一資源定位器)是URI的一個子集,它只包含了定位Web資源的足夠資訊;非URL的URI一般稱為URN(統一資源名稱);通過它並不能定位到資源的確切位置
Java中,URI類不包含任何用於訪問資源的方法,它唯一的作用就是對資源的URI字串進行解析;而URL類能開啟到達一個Java類庫知道如何處理的資源(http:、https:、ftp:、file:、jar:等)的流或串連
建立URL串連:URL類通過傳入的url字串來初始化對象;URL類中的openStream方法可以直接開啟一個訪問該資源“內容”的輸入資料流
如果需要獲得該資源的更多資訊,則應該使用URL類中的openConnection方法建立一個URLConnection對象;之後可以通過URLConnection中的set類型方法設定訪問請求的類型;再調用connect方法串連上資源;串連建立後就可以通過URLConnection的get類型方法獲得該資源的各種資訊了(其中getInputStream可以得到與URL類的openStream方法相同的內容輸入資料流)
提交表單資料:首先通過URL建立一個URLConnection對象;設定該對象的setDoOutput方法為true(預設為false);然後通過getOutputStream方法獲得向資源所在伺服器寫入資料的輸出資料流;調用輸出資料流的print方法,按照name=URLEncoder.encode(value, “UTF-8”)的格式寫入資料,多個資料間用“&”分隔;最後關閉輸出資料流,同時開啟一個輸入資料流來讀取伺服器的響應資訊
通訊端逾時:可以通過調用Socket類的setSoTimeout(int)方法設定通訊端的逾時時間,若之後的讀寫操作在沒有完成前就超過了時間限制,則會拋出SocketTimeoutException,捕獲該異常就可以對逾時做出響應的處理
此外,預設的構造體Socket(Stringhost, int port)實際上會阻塞當前線程直到串連建立;為了避免阻塞,可以先構造一個不需連線的通訊端,然後再使用一個逾時來進行串連,如下:
Socket s = new Socket();
s.connect(new InetSocketAddress(host, port), timeout);
可中斷的通訊端:線程因通訊端長時間無法響應而阻塞時,是無法通過interrupt來中斷的;為了中斷通訊端操作,java.nio包提供了一個SocketChannel類,該類通過靜態方法open綁定InetSocketAddress對象來建立通道(channel);通道與流無關,但可以通過實現自ReadableByteChannel介面和WritableByteChannel介面的read和write方法調用Buffer對象來實現資料的讀寫;任意時刻,通過close該通道,就可以中斷這個通訊端
半關閉:通過關閉一個通訊端的輸出資料流(shutdownOutput方法)來表示發送給伺服器的請求資料已結束,但是保留輸入資料流開啟來讀取伺服器返回的響應資訊,以此來告訴伺服器請求已經結束但通訊端並未關閉,適用於一站式的網路服務
網際網路地址:InetAddress類可以轉換主機名稱與網際網路IP地址,該類的對象可以通過靜態方法getByName(String host)建立
資料庫編程
建立JDBC串連:
1. 將資料庫JDBC驅動程式所在的庫添加到編譯環境中(修改CLASSPATH環境變數,或將驅動程式套件放到jre/lib/ext中)
2. 在驅動管理器中註冊驅動程式,可以通過System.setProperty(“jdbc.drivers”, “com.mysql.jdbc.Driver”),也可以通過Class.forName(“com.mysql.jdbc.Driver”)
3. 通過DriverManager開啟串連:DriverManager.getConnection(url, username, password);其中url是資料庫的URL,username是資料庫的賬戶名,password是密碼;返回一個Connection對象
執行SQL命令:Connection對象的方法createStatement可以建立一個Statement對象,該對象中的:
1. int executeUpdate(String sql)方法可以執行INSERT、DELETE、UPDATE操作,並返回影響的行數
2. ResultSet executeQuery(Stringsql)方法可以執行SELECT操作,返回的ResultSet對象中包含了查詢結果的表
3. boolean execute(String sql)方法可以執行任意的SQL語句,但通常用於互動式查詢
ResultSet對象:ResultSet對象可以通過next方法迭代每一行,在每一行中都可以通過getXXX(int)(如getDouble(3)等)方法訪問對應列(從1開始,而非0)的資料
Connection、Statement、ResultSet的關閉:用完它們時,應該調用close方法予以關閉
異常處理:通常採用如下的代碼風格以保證異常的正確處理:
Connection conn = …;
try {
…
} finally {
conn.close();
}
預備語句:預備一個帶有宿主變數的查詢語句,每次查詢的時候只需為該變數填入不同的字串就可以反覆多次地使用該語句;宿主變數用“?”表示;執行預備語句之前,必須使用相應的setXXX(int position, XXX value)方法將變數綁定到正確的值上(如setString(1,“CHN”)),其中position數值從1開始
預備語句PreparedStatement對象通過Connection的prepareStatement(Stringsql)方法獲得;它也包含了無參的int executeUpdate()和ResultSet executeQuery()方法執行SQL操作
可滾動和可更新的結果集:查詢對象和預備語句對象在被Connection建立時都有設定屬性的重載方法(createStatement(int type, int concurrency)和prepareStatement(Stringsql, int type, int concurrency)),其中type設定結果集ResultSet可否滾動,concurrency設定結果集可否更新資料庫;二者的取值都是ResultSet中的公有常量,如下:
type:TYPE_FORWARD_ONLY:預設,結果集不能滾動
TYPE_SCROLL_INSENSITIVE:可滾動,但不隨資料庫變化而變化
TYPE_SCROLL_SENSITIVE:可滾動,且隨資料庫變化而變化
concurrency:CONCUR_READ_ONLY:預設,修改結果集不能更新資料庫
CONCUR_UPDATABLE:修改結果集可同步修改資料庫
可滾動的結果集ResultSet除了有next方法向前遍曆外,還有previous方法向後遍曆,有relative(int)方法可以按當前位置向前(為正)或向後(為負)移動若干個,有absolute(int)方法指定移動到某行上,還有其它一些預設情況下停用方法(如first、last等)
可修改的結果集可以調用ResultSet類中提供的修改當前結果集對象內部資料的方法,如updateString、updateDouble等,但需要注意的是,必須指定列的名稱或序號(從1開始),最後需要調用updateRow方法向資料庫提交;同樣的,insertRow和deleteRow也是向資料庫提交插入行和刪除行的操作
中繼資料:通過Connection的getMetaData方法可以獲得DatabaseMetaData對象,該對象儲存了資料庫的結構資訊,如全部表的名字(getTables方法獲得)等,主要用於構建資料庫管理工具
行集:結果集ResultSet使用期間,程式必須與資料庫保持串連(即Connection不能close);一旦Connection關閉,ResultSet也隨之不可用;行集與結果集用於同樣的方法和功能,但不需要始終跟資料庫保持串連;行集的具體類視實現的庫而定,略
事務:一組SQL語句操作構建成一個事務;當所有的語句都順利執行後,事務被提交;否則,一旦某個語句出現錯誤,事務必須復原
JDBC預設下,資料庫處於自動認可模式(即每條SQL執行後都立即自動commit),無法進行交易處理;進行交易處理的步驟是:
1. 調用Connection對象的setAutoCommit(false),關閉自動認可模式
2. Statement對象或PreparedStatement對象執行若干次executeUpdate方法後
3. 調用Connection對象的commit方法,提交事務
4. 若期間出現錯誤或異常(SQLException),調用Connection對象的rollback方法進行復原
通常情況下,復原會從事務的開頭開始,即上一次調用commit之後;可以通過設定儲存點Savepoint對象來手動設定(通過Connection對象的setSavepoint方法建立)復原開始的點(調用rollback(Savepoint)方法);操作結束後,需要調用Connection對象的releaseSavepoint(Savepoint)釋放儲存點
批處理:Statement對象的addBatch(String sql)方法可以將一組SQL語句逐一添加到一個批處理序列中去;添加完成後,調用executeBatch方法可以一次性執行該序列中所有的SQL語句,並返回一個整型數值以記錄每條語句影響的行數;注意若是在批處理中執行SELECT操作將會拋出異常
進階串連管理:若資料庫連接管理與JNDI整合在一起,則不再使用DataManager來建立資料庫連接,Connection對象通過JDNI指定的資源類建立,如下例:
Context jndiConext = new InitialContext();
DataSource source = (DataSource)jndiContext.lookup(…);
Connection conn = source.getConnection();
常用於將資料庫連接與JNDI整合在一起的JavaEE環境中