201871010132--張瀟瀟--《面向對象程序設計(java)》第十六週學習總結

 

 
博文正文開頭格式:(2分)

項目html

內容java

這個做業屬於哪一個課程程序員

https://www.cnblogs.com/nwnu-daizh算法

這個做業的要求在哪裏數據庫

https://www.cnblogs.com/nwnu-daizh/p/12031970.html編程

 

 

 

做業學習目標canvas

(1) 掌握Java應用程序的打包操做;數組

(2) 掌握線程概念;緩存

(3) 掌握線程建立的兩種技術。安全

(4)學習應用程序的GUI

本章內容:
14.1 什麼是線程
Thread 類的靜態 sleep 方法將暫停給定的毫秒數。調用 Thread.sleep 不會建立一個新線程, sleep 是 Thread 類的靜態方法,用於暫停當前線程的活動。 sleep 方法能夠拋出一個 InterruptedException 異常。
java.lang.Thread 1.0
static void sleep(long millis)
休眠給定的毫秒數。
參數:millis 休眠的毫秒數
不要調用 Thread 類或 Runnable 對象的 run 方法。直接調用 run 方法,只會執行同一個線程中的任務,並不會啓動新線程。應該調用 Thread.start 方法。這個方法將建立一個執行 run 方法的新線程。
java.lang.Thread 1.0
Thread(Runnable target)
構造一個新線程,用於調用給定target的run()方法。
void start()
啓動這個線程,將引起調用run()方法。這個方法將當即返回,而且新線程將並行運行。
void run()
調用關聯Runnable的run方法。
java.lang.Runnable 1.0
void run()
必須覆蓋這個方法,並在這個方法中提供所要執行的任務指令。
14.2 中斷線程
當線程的 run 方法執行方法體重最後一條語句後,並經由執行 return 語句返回時,或者出現了在方法中沒有捕獲的異常時,線程將終止。在Java的早期版本中,還有一個 stop 方法,其餘線程能夠調用它終止線程。可是,這個方法如今已經被棄用了。
有一種能夠強制線程終止的方法。然而, interrupt 方法能夠用來請求終止線程。
當對一個線程調用 interrupt 方法時,線程的中斷狀態將被置位。這是每個線程都具備的 boolean 標誌。每一個線程都應該不時地檢查這個標誌,以判斷線程是否被中斷。
調用 Thread.currentThread().isInterrputed() 方法得到當前線程的中斷狀態是否被置位。可是,若是線程被阻塞,就沒法檢測中斷狀態。這是產生 InterruptedException 異常的地方。當在一個被阻塞的線程(調用 sleep 或 wait )上調用 interrupt 方法時,阻塞調用將會被 Interrupt Exception 異常中斷(存在不能被中斷的阻塞 I/O 調用,應該考慮選擇可中斷的調用)。
沒有任何語言方面的需求要求一個被中斷的線程應該終止。中斷一個線程不過是引發它的注意。被中斷的線程能夠決定如何響應中斷。某些線程是如此重要以致於應該處理完異常後,繼續執行,而不理會中斷。可是,更普通的狀況是,線程將簡單地將中斷做爲一個終止的請求。
若是在每次工做迭代以後都調用 sleep 方法(或者其餘的可中斷方法), isInterrpted 檢測既沒有必要也沒有用處。若是在種蒜狀態被置位時調用 sleep 方法,它不會休眠。相反,它將清除這一狀態(!)並拋出 InterrputedException 。所以,若是你的循環調用 sleep ,不會檢測中斷狀態,相反,須要捕獲 InterrputedException 異常。
有兩個很是相似的方法, interrupted 和 isInterrupted 。 Interrupted 方法是一個靜態方法,它檢測當前的線程是否被中斷。並且,調用 interrupted 方法會清除該線程的中斷狀態。另外一方面, isInterrupted 方法是一個實例方法,可用來檢驗是否有線程被中斷。調用這個方法不會改變中斷狀態。
java.lang.Thread 1.0
void interrupt()
向線程發送中斷請求。線程的中斷狀態將設置爲 true 。若是目前該線程被一個 sleep 調用阻塞,那麼, InterruptedException 異常被拋出。
static boolean interrupted()
測試當前線程(即正在執行這一命令的線程)是否被中斷。注意,這是一個靜態方法。這一調用會產生反作用-它將當前線程的中斷狀態重置爲false。
boolean isInterrupted()
測試線程是否被終止。不像靜態的中斷方法,這一調用不改變線程的中斷狀態。
static Thread currentThread()
返回表明當前執行線程的 Thread 對象。
14.3 線程狀態
線程能夠有以下6種狀態:
New (新建立)
Runnable (可運行)
Blocked (被阻塞)
Waiting (等待)
Timed waiting (計時等待)
Terminated (被終止)
14.3.1 新建立線程
當用new操做符建立一個新線程,如 new Thread(r) ,該線程尚未開始運行。這意味着它的狀態是 new 。當一個線程處於新建立狀態時,程序尚未開始運行線程中的代碼。在線程運行以前還有一些基本工做要作。
14.3.2 可運行線程
一旦調用 start 方法,線程處於 runnable 狀態。一個可運行的線程可能正在運行也可能沒有運行,這取決於操做系統給線程提供運行的時間。( Java 的規範說明沒有將它做爲一個單獨狀態。一個正在運行中的線程仍然處於可運行狀態。)
一旦一個線程開始運行,它沒必要始終保持運行。事實上,運行中的線程被中斷,目的是爲了讓其餘線程得到運行機會。線程調度的細節依賴於操做系統提供的服務。搶佔式調度系統給每個可運行線程一個時間片來執行任務。當時間片用完,操做系統剝奪該線程的運行權,並給另外一個線程運行機會。當選擇下一個線程時,操做系統考慮線程的優先級。
在任何給定時刻,一個可運行的線程可能正在運行也可能沒有運行(這就是爲何將這個狀態稱爲可運行而不是運行)。
14.3.3 被阻塞線程和等待線程
當線程處於被阻塞或等待狀態時,它暫時不活動。它不運行任何代碼且消耗最少資源。直到線程調度器從新激活它。細節取決於它是怎樣達到非活動狀態的。
當一個線程試圖獲取一個內部的對象鎖(而不是 java.util.concurrect 庫中的鎖),而該鎖被其餘線程持有,則該線程進入阻塞狀態。當全部其餘線程釋放該鎖,而且線程調度器容許本線程持有它的時候,該線程將變成非阻塞狀態。
當線程等待另外一個線程通知調度器一個條件時,它本身進入等待狀態。在調用 Object.wait 方法或 Thread.join 方法,或者是等待 java.util.concurrent 庫中的 Lock 或 Condition 時,就會出現這種狀況。實際上,被阻塞狀態與等待狀態是由很大不一樣的。
有幾個方法有一個超時參數。調用它們致使線程進入計時等待( timed waiting )狀態。這一狀態將一直保持到超時期滿或者接收到適當的通知。帶有超時參數的方法有 Thread.sleep 和 Object.wait 、 Thread.join 、 Lock.tryLock 以及 Condition.awit 的計時版。
當一個線程被阻塞或等待時(或終止時),另外一個線程被調度爲運行狀態。當一個線程被從新激活(例如,由於超時期滿或成功地得到一個鎖),調度器檢查它是否具備比當前運行線程更高的優先級。若是是這樣,調度器從當前運行線程中挑選一個,剝奪其運行權,選擇一個新的線程運行。
線程狀態圖
14.3.4 被終止的線程
線程因以下兩個緣由之一而被終止:
由於 run 方法正常退出而天然死亡。
由於一個沒有捕獲的異常終止了 run 方法而意外死亡。
特別是,能夠調用線程的 stop 方法殺死一個線程。該方法拋出 ThreadDeath 錯誤對象,由此殺死線程。可是, stop 方法已過期,不要在本身的代碼中調用這個方法。
java.lang.Thread 1.0
void join()
等待終止指定的線程。
void join(long millis)
等待指定的線程死亡或者通過指定的毫秒數。
Thread.State getState() 5.0
獲得這一線程的狀態:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING或TERMINATED之一。
void stop()
中止該線程。這一方法已過期。
void suspend()
暫停這一線程的執行。這一方法已過期。
void resume()
恢復線程。這一方法僅僅在調用suspend()以後調用。這一方法已過期。
14.4 線程屬性
線程的各類屬性,其中包括:線程優先級、守護線程、線程組以及處理未捕獲異常的處理器。
14.4.1 線程優先級
在 Java 程序設計語言中,每個線程有一個優先級。默認狀況下,一個線程繼承它的父線程的優先級。能夠用 setPriority 方法提升或下降任何一個線程的優先級。能夠將優先級設置爲在 MIN_PRIORITY (在 Thread 類中定義爲 1 )與 MAX_PRIORITY (定義爲 10 )之間的任何值。 NORM_PRIORITY 被定義爲 5 。
每當線程調度器有機會選擇新線程時,它首先選擇具備較高優先級的線程。可是,線程優先級是高度依賴於系統的。當虛擬機依賴於宿主機平臺的線程實現機制時, Java 線程的優先級被映射到宿主主機平臺的優先級上,優先級個數也許更多,也許更少。
Windows 有 7 個優先級別。一些 Java 優先級將映射到相同的操做系統優先級。在 Sun 爲 Linux 提供的 Java 虛擬機,線程的優先級被忽略-全部線程具備相同的優先級。
java.lang.Thread 1.0
void setPriority(int newPriority)
設置線程的優先級。優先級必須在Thread.MIN_PRIORITY與Thread.MAX_PRIORITY之間。通常使用Thread.NORM_PRIORITY優先級。
static int MIN_PRIORITY
線程的最小優先級。最小優先級的值爲1。
static int NORM_PRIORITY
線程的默認優先級。默認優先級爲5。
static int MAX_PRIORITY
線程的最高優先級。最高優先級的值爲10。
static void yield()
致使當前執行線程處於讓步狀態。若是有其餘的可運行線程具備至少與此線程一樣高的優先級,那麼這些線程接下來會被調度。注意,這是一個靜態方法。
14.4.2 守護線程
能夠經過 t.setDaemon(true) 將線程轉換爲守護線程( daemon thread )。守護線程的惟一用途是爲其餘線程提供服務。計時線程就是一個例子,它定時地發送「計時器嘀嗒」信號給其餘線程或清空過期的高速緩存項的線程。當只剩下守護線程時,虛擬機就退出了,因爲若是隻剩下守護線程,就不必繼續運行程序了。
守護線程應該永遠不去訪問固有資源,如文件、數據庫,由於它會在任什麼時候候甚至在一個操做的中間發生中斷。
java.lang.Threed 1.0
void setDaemon(boolean isDaemon)
標識該線程爲守護線程或用戶線程。這一方法必須在線程啓動以前調用。
14.4.3 未捕獲異常處理器
線程的 run 方法不能拋出任何被檢測的異常,可是,不被檢測的異常會致使線程終止。在這種狀況下,線程就死亡了。
可是,不須要任何 catch 子句來處理能夠被傳播的異常。相反,就在線程死亡以前,異常被傳遞到一個被用於未捕獲異常的處理器。
該處理器必須屬於一個實現 Thread.UncaughtExceptionHandler 接口的類。這個接口只有一個方法。
 void uncaughtException(Thread t,Throwable e)
能夠用 setUncaughtExceptionHandler 方法爲任何線程安裝一個處理器。也能夠用 Thread 類的靜態方法 setDefaultUncaughtExceptionHandler 爲全部線程安裝一個默認的處理器。替換處理器可使用日誌API發送未捕獲異常的報告到日誌文件。
若是不安裝默認的處理器,默認的處理器爲空。可是,若是不爲獨立的線程安裝處理器,此時的處理器就是該線程的 ThreadGroup 對象。
線程組是一個能夠統一管理的線程集合。默認狀況下,建立的全部線程屬於相同的線程組,可是,也可能會創建其餘的組。如今引入了更好的特性用於線程集合的操做,因此建議不要在本身的程序中使用線程組。
ThreadGroup 類實現 Thread.UncaughtExceptionHandler 接口。它的 uncaughtException 方法作以下操做:
1)若是該線程組有父線程組,那麼父線程組的 uncaughtException 方法被調用。
2)不然,若是 Thread.getDefaultExceptionHandler 方法返回一個非空的處理器,則調用該處理器。
3)不然,若是 Throwable 是 ThreadDeath 的一個實例,什麼都不作。
4)不然,線程的名字以及 Throwable 的棧跟蹤被輸出到 System.err 上。
java.lang.Thread 1.0
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 5.0
設置或獲取未捕獲異常的默認處理器。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() 5.0
設置或獲取未捕獲異常的處理器。若是沒有安裝處理器,則將線程組對象做爲處理器。
java.lang.Thread.UncaughtExceptionHandler 5.0
void uncaughtException(Thread t,Throwable e)
當一個線程因未捕獲異常而終止,按規定要將客戶報告記錄到日誌中。
參數:t 因爲未捕獲異常而終止的線程
e 未捕獲的異常對象
java.lang.ThreadGroup 1.0
void uncaughtException(Thread t,Throwable e)
若是有父線程組,調用父線程組的這一方法;或者,若是 Thread 類有默認處理器,調用該處理器,不然,輸出棧蹤影到標準錯誤流上(可是,若是 e 是一個 ThreadDeath 對象,棧蹤影是被禁用的。 ThreadDeath 對象 stop 方法產生,而該方法已通過時)。
14.5 同步
若是兩個線程存取相同的對象,而且每個線程都調用了一個修改該對象狀態的方法,這樣一個狀況一般稱爲競爭條件( race condition )。
14.5.1 競爭條件的一個例子
銀行轉帳例子
14.5.2 競爭條件詳解
一條名利是由幾條指令組成的,執行它們的線程能夠在任何一條指令點上被中斷。
14.5.3 鎖對象
有兩種機制防止代碼塊受併發訪問的干擾。Java語言提供一個 synchronized 關鍵字達到這一目的,而且 Java SE 5.0 引入了 ReentrantLock 類。 synchronized 關鍵字自動提供了一個鎖以及相關的「條件」,對於大多數須要顯示鎖的狀況,這是很遍歷的。 java.util.concurrent 框架爲這些基礎機制提供獨立的類。
用 ReentrantLock 保護代碼塊的基本結構以下:
 myLock.lock(); //a ReentrantLock object
 try
 {
     critical section
 }
 finally
 {
     myLock.unlock();//make sure the lock is unlocked even if an exception is three
 }
這一結構確保任什麼時候刻只有一個線程進入臨界區。一旦一個線程封鎖了鎖對象,其餘任何線程都沒法經過 lock 語句。當其餘線程調用 lock 時,它們被阻塞,直到第一個線程釋放鎖對象。
把解鎖操做放在 finally 子句以內是相當重要的。若是在臨界區的代碼拋出異常,鎖必須被釋放。不然,其餘線程將永遠阻塞。
若是使用鎖,就不能使用帶資源的 try 語句。首先,解鎖方法名不是 close 。不過,即便將它重命名,帶資源的 try 語句也沒法正常工做。它的首部但願聲明一個新變量。可是若是使用一個鎖,可能想使用多個線程共享的那個變量(而不是新變量)。
鎖是可重入的,由於線程能夠重複地得到已經持有的鎖。鎖保持一個持有計數( hold count )來跟蹤對 lock 方法的嵌套調用。線程在每一次調用lock都要調用 unlock 來釋放鎖。因爲這一特性,被一個鎖保護的代碼能夠調用另外一個使用相同的鎖的方法。
一般,可能想要保護需若干個操做來更新或檢查共享對象的代碼塊。要確保這些操做完成後,另外一個線程才能使用相同對象。
要留心臨界區中的代碼,不要由於異常的拋出而跳出了臨界區。若是在臨界區代碼結束以前拋出了異常, finally 子句將釋放鎖,但會使對象可能處於一種受損狀態。
java.util.concurrent.locks.Lock 5.0
void lock()
獲取這個鎖;若是鎖同時被另外一個線程擁有則發生阻塞。
void unlock()
釋放這個鎖。
java.util.concurrent.locks.ReentrantLock 5.0
ReentrantLock()
構建一個能夠被用來保護臨界區的可重入鎖。
ReentrantLock(boolean fair)
構建一個帶有公平策略的鎖。一個公平鎖偏心等待時間最長的線程。可是麼這一公平的保證將大大下降性能。因此,默認狀況下,鎖沒有被強制爲公平的。
聽起來公平鎖更合理一些,可是使用公平鎖比使用常規鎖要慢不少。只有當你確實瞭解本身要作什麼而且對於你要解決的問題有一個特定的理由必須使用公平鎖的時候,纔可使用公平鎖。即便使用公平鎖,也沒法確保線程調度器是公平的。若是線程調度器選擇忽略一個線程,而該線程爲了這個鎖已經等待了很長時間,那麼就沒有機會公平地處理這個鎖了。
14.5.4 條件對象
一般,線程進入臨界區,卻發如今某一條件知足以後它才能執行。要使用一個條件對象來管理那些已經得到了一個鎖可是卻不能作有用工做的線程。因爲歷史的緣由,條件對象常常被稱爲條件變量( conditional variable )。
一個鎖對象能夠有一個或多個相關的條件對象。能夠用 newCondition 方法得到一個條件對象。習慣上給每個條件對象命名爲能夠反映它鎖表達的條件的名字。若是條件不知足,調用 Condition.await() 。當前線程如今被阻塞了,並放棄了鎖。
等待得到鎖的線程和調用 await 方法的線程存在本質上的不一樣。一旦一個線程調用 await 方法,它進入該條件的等待集。當鎖可用時,該線程不能立刻解除阻塞。相反,它處於阻塞狀態,直到另外一個線程調用同一條件上的 singalAll 方法時爲止。 singalAll() 調用從新激活由於這一條件而等待的全部線程。當這些線程從等待集中移出時,它們再次成爲可運行的,調度器將再次激活它們。同時,它們將試圖從新進入該對象。一旦鎖成爲可用的,它們中的某個將從 await 調用返回,得到該鎖並從被阻塞的地方繼承執行。
此時,線程應該再次測試該條件。因爲沒法確保該條件被知足 -signalAll 方法僅僅是通知正在等待的線程:此時有可能已經知足條件,值得再次去檢測該條件。
一般,對 await 的調用應該在以下形式的循環體中:
 while(!(ok to proceed))
     condition.await();
相當重要的是最終須要某個其餘線程調用 signalAll 方法。當一個線程調用 await 時,它沒有辦法從新激活自身。它寄但願於其餘線程。若是沒有其餘線程來從新激活等待的線程,它就永遠再也不運行了。這將致使使人不快的死鎖( deadlock )現象。若是全部其餘線程被阻塞,最後一個活動線程在解除其餘線程的阻塞狀態以前就調用 await 方法,那麼它也被阻塞。沒有任何線程能夠解除其餘線程的阻塞,那麼該程序就掛起了。
應該什麼時候調用 signalAll 呢?經驗上講,在對象的狀態有利於等待線程的方向改變時調用 signalAll 。
注意調用 signalAll 不會當即激活一個等待線程。它僅僅解除等待線程的阻塞,以便這些線程能夠在當前線程退出同步方法以後,經過競爭實現對對象的訪問。
另外一個方法 signal ,則是隨機解除等待集中某個線程的阻塞狀態。這比解除全部線程的阻塞更加有效,但也存在危險。若是隨機選擇的線程發現本身仍然不能運行,那麼它再次被阻塞。若是沒有其餘線程再次調用 signal ,那麼系統就死鎖了。
當一個線程擁有某個條件的鎖時,它僅僅能夠在該條件上調用 await 、 signalAll 或 signal 方法。
java.util.concurrent.locks.Lock 5.0
Condition newCondition()
返回一個與該鎖相關的條件對象。
java.util.concurrent.locks.Condition 5.0
void await()
將該線程放到條件的等待集中。
void signalAll()
解除該條件的等待集中的全部線程的阻塞狀態。
void signal()
從該條件的等待集中隨機地選擇一個線程,解除其阻塞狀態。
14.5.5 synchronized關鍵字
鎖和條件的關鍵之處:
鎖用來保護代碼片斷,任什麼時候刻只能有一個線程執行被保護的代碼。
鎖能夠管理試圖進入被保護代碼段的線程。
鎖能夠擁有一個或多個相關的條件對象。
每一個條件對象管理那些已經進入被保護的代碼段但還不能運行的線程。
從 1.0 版開始, Java 中的每個對象都有一個內部鎖。若是一個方法用 synchronized 關鍵字聲明,那麼對象的鎖將保護整個方法。也就是說,要調用該方法,線程必須得到內部的對象鎖。
內部對象鎖只有一個相關條件。 wait 方法添加一個線程到等待集中, notifyAll/notify 方法解除等待線程的阻塞狀態。換句話說,調用 wait 或 notifyAll 等價於
 intrinsicCondition.await();
 intrinsicCondition.signalAll();
wait 、 notifyAll 以及 notify 方法是 Object 類的 final 方法。 Condition 方法必須被命名爲 await 、 signalAll 和 signal 以便它們不會與那些方法發生衝突。
將靜態方法聲明爲 synchronized 也是合法的。若是調用這個方法,該方法得到相關的類對象的內部類。所以,沒有其餘線程能夠調用同一個類的這個或任何其餘的同步靜態方法。
內部鎖和條件存在一些侷限。包括:
不能中斷一個正在試圖得到鎖的線程。
試圖得到鎖時不能設定超時。
每一個鎖僅有單一的條件,多是不夠的。
在代碼中應該使用哪種? Lock 和 Conditon 對象仍是同步方法?下面是一些建議:
最好既不使用 Lock/Condition 也不實用 synchronized 關鍵字。在許多狀況下你可使用 java.util.concurrent 包中的一種機制,它會爲你處理全部的加鎖。
若是 synchronized 關鍵字適合你的程序,那麼請儘可能使用它。這樣能夠減小編寫的代碼數量,減小出錯的概率。
若是特別須要 Lock/Condition 結構提供的特有特性時,才使用 Lock/Condition 。
java.lang.Object 1.0
void notifyAll()
解除那些在該對象上調用 wait 方法的線程的阻塞狀態。該方法只能在同步方法或同步塊內部調用。若是當前線程不是對象鎖的持有者,該方法拋出一個 IllegalMonitorStateException 異常。
void notify()
隨機選擇一個在該對象上調用 wait 方法的線程,解除其阻塞狀態。改方法只能在一個同步方法或同步塊中調用。若是當前線程不是對象鎖的持有者,該方法拋出一個 IllegalMonitorStateException 異常。
void wait()
致使線程進入等待狀態直到它被通知。該方法只能在一個同步方法中調用。若是當前線程不是對象鎖的持有者,該方法拋出一個 IllegalMonitorStateException 異常。
void wait(long millis)
void wait(long millis,int nanos)
致使線程進入等待狀態直到它被通知或者通過指定的時間。這些方法只能在一個同步方法中調用。若是當前線程不是對象鎖的持有者該方法拋出一個 IllegalMonitorStateException 異常。
參數:millis 毫秒數
nanos 納秒數,<1 000 000
14.5.6 同步阻塞
每個 Java 對象有一個鎖。線程能夠經過調用同步方法得到鎖。還有另外一種機制能夠得到鎖,經過進入一個同步阻塞。當線程進入以下形式的阻塞:
 synchronized(obj) //this is the     synchronized block
 {
     critical section
 }
因而它得到 obj 的鎖。
使用一個對象的鎖來實現額外的原子操做,實際上稱爲客戶端鎖定( client-slide locking )。客戶端鎖定是很是脆弱的,一般不推薦使用。
14.5.7 監視器概念
鎖和條件是線程同步的強大工具,可是,嚴格來說,它們不是面向對象的。多年來,研究人員努力尋找一種方法,能夠在不須要程序員考慮如何加鎖的狀況下,就能夠保證多線程的安全性。最成功的解決方案之一是監視器( monitor ),這一律念最先是由 Per Brinch Hansen 和 Tony Hoare 在 20 世紀 70 年代提出的。用 Java 的術語來說,監視器具備以下特性:
監視器是隻包含私有域的類。
每一個監視器的對象有一個相關的鎖。
使用該鎖對全部的方法進行加鎖。
該鎖能夠有任意多個相關條件。
Java 設計者以不是很精確的方式採用了監視器概念, Java 中的每個對象有一個內部的鎖和內部的條件。若是一個方法用 synchronized 關鍵字聲明,那麼,它表現的就像是一個監視器方法。經過調用 wait/notifyAll/notify 訪問條件變量。
在下述的 3 個方面 Java 對象不一樣於監視器,從而使得線程的安全性降低。
域不要求必須是 private 。
方法不要求必須是 synchronized 。
內部鎖對客戶是可用的。
14.5.8 Volatile 域
有時,僅僅爲了讀寫一個或兩個實例域就使用同步,顯得開銷過大了。畢竟,什麼地方能出錯呢?遺憾的是,使用現代的處理器與編譯器,出錯的可能性很大。
多處理器的計算機可以暫時在寄存器或本地內存緩衝區中保存內存中的值。結果是,運行在不一樣處理器上的線程可能在同一個內存位置取到不一樣的值。
編譯器能夠改變指令執行的順序以使吞吐量最大化。這種順序上的變化不會改變代碼語義,可是編譯器假定內存的值僅僅在代碼中有顯式的修改指令時纔會改變。然而,內存的值能夠被另外一個線程改變。
若是你使用鎖來保護能夠被多個線程訪問的代碼,那麼能夠不考慮這種問題。編譯器被要求經過在必要的時候刷新本地緩存來保持鎖的效應,而且不能不正當地從新排序指令。
volatile 關鍵字爲實例域的同步訪問提供了一種免鎖機制。若是聲明一個域爲 volatile ,那麼編譯器和虛擬機就知道該域是可能被另外一個線程併發更新的。
Volatile 變量不能提供原子性。例如,方法
private volatile boolean done;
public void flipDone(){done = !done;} //not atomic
不能確保翻轉域中的值。
14.5.9 final 變量
除非使用域或 volatile 修飾符,不然沒法從多個線程安全地讀取一個域。還有一種狀況能夠安全地訪問一個共享域,即這個域聲明爲 final 時。考慮如下聲明:
 final Map<String,Double> accounts = new HashMap();
其餘線程會在構造函數完成構造以後纔看到這個 accounts 變量。
若是不使用 final ,就不能保證其餘線程看到的是 accounts 更新後的值,它們可能都只是看到 null ,而不是新構造的 HashMap 。
對這個映射表的操做並非線程安全的。若是多個線程在讀寫這個映射表,仍然須要進行同步。
14.5.10 原子性
假設對共享變量除了賦值以外並不完成其餘操做,那麼能夠將這些共享變量聲明爲 volatic 。
java.util.concurrent.atomic 包中有不少類使用了很高效的機器級指令(而不是使用鎖)來保證其餘操做的原子性。例如, AtomicInteger 類提供了方法 incrementAndGet 和 decrementAndGet ,它們分別以原子方式將一個整數自增或自減。能夠安全地使用 AtomicInteger 做爲共享計數器而無須同步。
另外這個包中還包含 AtomicBoolean 、 AtomicLong 和 AtomicReference 以及 Boolean 值、整數、 long 值和引用的原子數組。應用程序員不該該使用這些類,它們僅供那些開發併發工具的系統程序員使用。
14.5.11 死鎖
有可能會由於每個線程要等待條件而致使全部線程都被阻塞。這樣的狀態稱爲死鎖( deadlock )。
Java 編程語言中沒有任何東西能夠避免或打破這種死鎖現象,必須仔細設計程序,以確保不會出現死鎖。
14.5.12 線程局部變量
有時可能要避免共享變量,使用 ThreadLocal 輔助類爲各個線程提供各自的實例。
要爲每一個線程構造一個實例,可使用如下代碼:
 public static final ThreadLocal< SimpleDateFormat > dateFormat = new ThreadLocal< SimpleDateFomrat >()
 {
     protected SimpleDateFormat initialValue()
     {
         return new SimpleDateFormat("yyyy-MM-dd");
     }
 }
要訪問具體的格式化方法,能夠調用:
 String dateStamp = dateFormat.get().format(new Date());
在一個給定線程中首次調用 get 時,會調用 initilaValue 方法。在此以後, get 方法會返回屬於當前線程的那個實例。
在多個線程中生成隨機數也存在相似的問題。 java.util.Random 類是線程安全的。可是若是多個線程須要等待一個共享的隨機數生成器,這會很低效。
可使用 ThreadLocal 輔助類爲各個線程提供一個單獨的生成器,不過 Java SE 7 還另外提供一個便利類。只須要作一下調用:
 int random = ThreadLocalRandom.current().nextInt(upperBound);
ThreadLocalRandom.current() 調用會返回特定於當前線程的 Random 類實例。
java.lang.ThreadLocal< T > 1.2
T get()
獲得這個線程的當前值。若是是首次調用get,會調用 initialize 來獲得這個值。
protected initialize()
應覆蓋這個方法來提供一個初始值。默認狀況下,這個方法返回 null 。
void set(T t)
爲這個線程設置一個新值。
void remove()
刪除對應這個線程的值。
java.util.concurrent.ThreadLocalRandom 7
static ThreadLocalRandom current()
返回特定於當前線程的 Random 類實例。
14.5.13 鎖測試與超時
線程在調用 lock 方法來得到另外一個線程所持有的鎖的時候,極可能發生阻塞。應該更加謹慎地申請鎖。
TimeUnit 是一個枚舉類型,能夠取的值包括 SECONDS 、 MILLISECONDS 、 MICROSECONDS 和 NANOSECONDS 。
lock 方法不能被中斷。若是一個線程在等待得到一個鎖時被中斷,中斷線程在得到鎖以前一直處於阻塞狀態。若是出現死鎖,那麼, lock 方法就沒法終止。
然而,若是調用帶有用超時參數的 tryLock ,那麼若是線程在等待期間被中斷,將拋出 InterruptedException 異常。這是一個很是有用的特性,由於容許程序打破死鎖。
也能夠調用 lockInterruptibly 方法。它就至關於一個超時設爲無限的 tryLock 方法。
在等待一個條件時,也能夠提供一個超時:
 myCondition.await(100,TimeUnit.MILLISECONDS)
若是一個線程被另外一個線程經過調用 signalAll 或 signal 激活,或者超時時限已達到,或者線程被中斷,那麼 await 方法將返回。
若是等待的線程被中斷, await 方法將拋出一個 InterruptedException 異常。在你但願出現這種狀況時線程繼續等待(可能不太合理),可使用 awaitUniterruptibly 方法代替 await 。
java.util.concurrent.locks.Lock 5.0
boolean tryLock()
嘗試得到鎖而沒有發生阻塞;若是成功返回真。這個方法會搶奪可用的鎖,即便該鎖有公平加鎖策略,即使其餘線程已經等待好久也是如此。
boolean tryLock(long time,TimeUnit unit)
嘗試得到鎖,阻塞時間不會超過給定的值;若是成功返回 true 。
void lockInterruptibly()
得到鎖,可是會不肯定地發生阻塞。若是線程被中斷,拋出一個 InterruptedException 異常。
java.util.concurrent.locks.Condition 5.0
boolean await(long time,TimeUnit unit)
進入該條件的等待集,直到線程從等待集中移出或等待了指定的時間以後才解除阻塞。。若是由於等待時間到了而返回就返回 false ,不然返回 true 。
void awaitUninterruptinly()
進入該條件的等待集,直到線程從等待集移出才解除阻塞。若是線程被中斷,該方法不會拋出 InterruptedException 異常。
14.5.14 讀/寫鎖
java.util.concurrent.locks包定義了兩個鎖類, ReentrantLock 類和 ReentrantReadWriteLock 類。若是不少線程從一個數據結構讀取數據而不多線程修改其中數據的話,後者是十分有用的。在這種狀況下,容許對讀者共享訪問是合適的。固然,寫者線程依然必須是互斥訪問的。
使用讀/寫鎖的必要步驟:
(1)構造一個ReentrantReadWriteLock對象:
 private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
(2)抽取讀鎖和寫鎖:
 private Lock readLock = rwl.readLock();
 private Lock writeLock = rwl.writeLock();
(3)對全部的獲取方法加讀鎖:
 public double getTotalBalance()
 {
     readLock.lock();
     try{...}
     finally{readLock.unlock();}
 }
(4)對全部的修改方法加寫鎖:
 public void transfer(...)
 {
     writeLock.lock();
     try{...}
     finally{writeLock.unlock();}
 }
java.util.concurrent.locks.ReentrantReadWriteLock 5.0
Lock readLock()
獲得一個能夠被多個讀操做共用的讀鎖,但會排斥全部寫操做。
Lock writeLock()
獲得一個寫鎖,排斥全部其餘的讀操做和寫操做。
14.5.15 爲何棄用stop和suspend方法
初始的Java版本定義了一個 stop 方法用來終止一個線程,以及一個 suspend 方法來阻塞一個線程直至另外一個線程調用 resume 。 stop 和 suspend 方法有一些共同點:都試圖控制一個給定線程的行爲。
這兩個方法已經棄用。 stop 方法天生就不安全,經驗證實 suspend 方法會常常致使死鎖。
stop 方法終止全部未結束的方法,包括 run 方法。當線程被終止,當即釋放被它鎖住的全部對象的鎖。這會致使對象處於不一致的狀態。
當線程要終止另外一個線程時,沒法知道何時調用 stop 方法是安全的,何時致使對象被破壞。所以,該方法被棄用了。在但願中止線程的時候應該中斷線程,被中斷的線程會在安全的時候中止。
一些做者聲稱 stop 方法被棄用是由於它會致使對象被一個已中止的線程永久鎖定。可是,這一說法是錯誤的。從技術上講,被中止的線程經過拋出 ThreadDeath 異常退出全部它所調用的同步方法。結果是,該線程釋放它持有的內部對象鎖。
與 stop 不一樣, suspend 不會破壞對象。可是,若是用 suspend 方法的線程試圖得到同一個鎖,那麼,該鎖在恢復以前是不可用的。若是調用 suspend 方法的線程試圖得到同一個鎖,那麼程序死鎖:被掛起的線程等着被恢復,而將其掛起的線程等待得到鎖。
若是想安全地掛起線程,引入一個變量 suspendRequested 並在 run 方法的某個安全的地方測試它,安全的地方是指該線程沒有封鎖其餘線程須要的對象的地方。當該線程發現 suspendRequested 變量已經設置,將會保持等待狀態直到它再次得到爲止。
14.6 阻塞隊列
對於許多線程問題,能夠經過使用一個或多個隊列以優雅且安全的方式將其形式化。
當試圖向隊列添加元素而隊列已滿,或是想從隊列移除元素而隊列爲空的時候,阻塞隊列( bolcking queue )致使線程阻塞。在協調多個線程以前的合做時,阻塞隊列是一個有用的工具。工做者線程能夠週期性地將中間結果存儲在阻塞隊列中。其餘的工做者線程移出中間結果並進一步加以修改。隊列會自動的平衡負載。若是第一個線程集運行得比第二個慢,第二個線程集在等待結果時會阻塞。若是第一個線程集運行得快,它將等待第二個隊列集遇上來。
阻塞隊列方法
方法
正常動做
特殊狀況下的動做
add
添加一個元素
若是隊列滿,則拋出IllegalStateException異常
element
返回隊列的頭元素
若是隊列空,拋出NoSuchElementException異常
offer
添加一個元素並返回true
若是隊列滿,返回false
peek
返回隊列的頭元素
若是隊列空,則返回null
poll
移出並返回隊列的頭元素
若是隊列空,則返回null
put
添加一個元素
若是隊列滿,則阻塞
remove
移出並返回頭元素
若是隊列空,則拋出NoSuchElementException異常
take
移出並返回頭元素
若是隊列空,則阻塞
阻塞隊列方法分爲如下 3 類,這取決於當隊列滿或空時它們的響應方式。若是將隊列看成線程管理工具來使用,將要用到 put 和 take 方法。當試圖向滿的隊列中添加或從空的隊列中移出元素時, add 、remove和element操做拋出異常。固然,在一個多線程程序中,隊列會在任什麼時候候空或滿,所以,必定要使用offer、poll和peek方法做爲替代。這些方法若是不能完成任務,只是給出一個錯誤提示而不會拋出異常。
poll和peek方法返回空來指示失敗。所以,向這些隊列中插入null值是非法的。
還有帶有超市的offer方法和poll方法的變體。例如,下面的調用:
 boolean success = q.offer(x,100,TimeUnit.MILLISECONDS);
嘗試在100毫秒的時間內在隊列的尾部插入一個元素。若是成功返回true;不然,達到超時時,返回false。相似地,下面的調用:
 Object head = q.poll(100,TimeUnit.MILLISEDS);
嘗試用100毫秒的時間移除隊列的頭元素;若是成功返回頭元素,不然,達到在超時時,返回null。
若是隊列滿,則put方法阻塞;若是隊列空,則take方法阻塞。在不帶超時參數時,offer和poll方法等效。
java.util.concurrent包提供了阻塞隊列的幾個變種。默認狀況下,LinkedBlockingQueue的容量是沒有上邊界的,可是,也能夠選擇指定最大容量。LinkedBlockingDeque是一個雙端的版本。ArrayBlockingQueue在構造時須要指定容量,而且有一個可選的參數來指定是否須要公平性。若設置了公平參數,則那麼等待了最長時間的線程會優先獲得處理。一般,公平性會下降性能,只有在確實很是須要時才使用它。
PriorityBlockingQueue是一個帶優先級的隊列,而不是先進先出隊列。元素按照它們的優先級順序被移出。該隊列是沒有容量上限,可是,若是隊列是空的,取元素的操做會阻塞。
DelayQueue包含是Delayed接口的對象:
 interface Delayed extends Comparable< Delayed >
 {
     long getDelay(TimeUnit unit);
 }
getDelay方法返回對象的殘留延遲。負值表示延遲已經結束。元素只有在延遲用完的狀況下才能從DelayQueue移除。還必須實現compareTo方法。DelayQueue使用該方法對元素進行排序。
Java SE 7增長了一個TransferQueue接口,容許生產者線程等待,直到消費者準備就緒能夠接收一個元素。若是生產者調用
q.transfer(item);
這個調用會阻塞,直到另外一個線程將元素(item)刪除。LinkedTransferQueue實現了這個接口。
java.util.concurrent.ArrayBlockingQueue< E > 5.0
ArrayBlockingQueue(int capacity)
ArrayBlockingQueue(int capacity,boolean fair)
構造一個帶有指定的容量和公平性的阻塞隊列。該隊列用循環數組實現。
java.util.concurrent.LinkedBlockingQueue< E > 5.0
java.uti..concurrent.LinkedBlockingDeque< E > 6.0
LinkedBlockingQueue()
LinkedBlockingDeque()
構造一個無上限的阻塞隊列或雙向隊列,用鏈表實現。
LinkedBolckingQueue(int capacity)
LinkedBlockingDeque(int capacity)
根據指定容量構建一個有限的阻塞隊列或雙向隊列,用鏈表實現。
java.util.concurrent.DelayQueue< E extends Delayed > 5.0
DelayQueue()
構造一個包含Delayed元素的無界的阻塞時間有限的阻塞隊列。只有那些延遲已經超過期間的元素能夠從隊列中移出。
java.util.concurrent.Delayed 5.0
long getDelay(TimeUnit unit)
獲得該對象的延遲,用給定的時間單位進行度量。
java.util.concurrent.PriorityBlockingQueue< E > 5.0
PriorityBlockingQueue()
PriorityBlockingQueue(int initialCapacity)
PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator)
構造一個無邊界阻塞優先隊列,用堆實現。
參數:initialCapacity 優先隊列的初始容量。默認值是11。
comparator 用來對元素進行比較的比較器,若是沒有指定,則元素必須實現Comparable接口。
java.util.concurrent.BlockingQueue< E > 5.0
void put(E element)
添加元素,在必要時阻塞。
E take()
移除並返回頭元素,必要時阻塞。
boolean offer(E element,long time,TimeUnit unit)
添加給定的元素,若是成功返回true,若是必要時阻塞,直至元素已經被添加或超時。
E poll(long time,TimeUnit unit)
移除並返回頭元素,必要時阻塞,直至元素可用或超時用完。失敗時返回null。
java.util.concurrent.BolckingDeque< E > 6
void putFirst(E element)
void putLast(E element)
添加元素,必要時阻塞。
E takeFirst()
E takeLast()
移除並但會頭元素或尾元素,必要時阻塞。
boolean offerFirst(E element,long time,TimeUnit unit)
boolean offerLast(E element,long time,TimeUnit unit)
添加給定的元素,成功時返回true,必要時阻塞直至元素被添加或超時。
E pollFirst(long time,TimeUnit unit)
E pollLast(long time,TimeUnit unit)
移動並返回頭元素或尾元素,必要時阻塞,直至元素可用或超時。失敗時返回null。
java.util.concurrent.TransferQueue< E > 7
void transfer(E element)
boolean tryTransfer(E element,long time,TimeUnit unit)
傳輸一個值,或者嘗試在給定的超時時間內傳輸這個值,這個調用將阻塞,直到另外一個線程將元素刪除。第二個方法會在調用成功時返回true。
14.7 線程安全的集合
能夠經過提供鎖來保護共享數據結構,可是選擇線程安全的實現做爲替代可能更容易寫。
14.7.1 高效的映射表、集合和隊列
java.util.concurrent包提供了映射表、有序集合隊列的高效實現:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。這些集合使用複雜的算法,經過容許併發地訪問數據結構的不一樣部分來使競爭極小化。
與大多數集合不一樣,size方法沒必要在常量時間內操做。肯定這樣的集合當前的大小一般須要遍歷。
集合返回弱一致性(weakly consisteut)的迭代器。這意味着迭代器不必定能反映出它們被構造以後的全部的修改,可是,它們不會將同一個值返回兩次,也不會拋出Concurrent ModificationException異常。
與之造成對照的是,集合若是在迭代器構造以後發生改變,java.util包中的迭代器將拋出一個ConcurrentModificationException異常。
併發地散列映射表,可高效地支持大量的讀者和必定數量的寫者。默認狀況下,假定能夠有多達16個寫者線程同時執行。能夠有更多的寫者線程,可是,若是同一時間多於16個,其餘線程將暫時被阻塞。能夠指定更大數目的構造器,然而,沒有這種必要。
ConcurrentHashMap和ConcurrentSkipListMap類有相應的方法用於原子性的關聯插入以及關聯刪除。putIfAbsent方法自動地添加新的關聯,前提是原來沒有這一關聯。對於多線程訪問的緩存來講這是頗有用的,確保只有一個線程向緩存添加項:
 cache.putIfAbsent(key,value);
相反的操做是刪除(或許應該叫作removeIfPresent)。調用
 cache.remove(key,value);
將原子性地刪除鍵值對,若是它們在映像表中出現的話。最後,
cache.replace(key,oldValue,newValue);
原子性地用新值替換舊值,假定舊值與指定的鍵值關聯。
java.util.concurrent.ConcurrentLinkedQueue< E > 5.0
ConcurrentLinkedQueue< E >()
構造一個能夠被多線程安全訪問的無邊界非阻塞的隊列。
java.util.concurrent.ConcurrentLinkedQueue< E > 5.0
ConcurrentSkipListSet< E >()
ConcurrentSkipListSet< E >(Comparator<? super E> comp)
構造一個能夠被多線程安全訪問的有序集。第一個構造器要求元素實現Comparable接口。
java.util.concurrent.ConcurrentHashMap< K,V > 5.0
java.util.concurrent.ConcurrentSkipListMap< K,V > 6
ConcurrentHashMap< K,V >()
ConcurrentHashMap< K,V >(int initialCapacity)
ConcurrentHashMap< K,V >(int initialCapacity,float loadFactor,int concurrencyLevel)
構造一個能夠被多線程安全訪問的散列映射表。
參數:initialCapacity 集合的初始容量。默認值爲16。
loadFactor 控制調整:若是每個桶的平均負載超過這個因子,表的大小會被從新調整。默認值是0.75。
concurrencyLevel 併發寫者線程的估計數目。
ConcurrentSkipListMap< K,V >()
ConcurrentSkipListSet< K,V >(Comparator<? super K> comp)
構造一個能夠被多線程安全訪問的有序的映像表。第一個構造器要求鍵實現Comparable接口。
V putIfAbsent(K key,V value)
若是該鍵沒有在映像表中出現,則將給定的值同給定的鍵關聯起來,並返回null。不然返回與該鍵關聯的現有值。
boolean remove(K hey,V value)
若是給定的鍵與給定的值關聯,刪除給定的鍵與值並返回真。不然,返回false。
boolean replace(K key,V oldValue,V newValue)
若是給定的鍵當前與oldvalue相關聯,用它與newValue關聯。不然,返回false。

第二部分 實驗總結
實驗1: 導入第13章示例程序,測試程序並進行代碼註釋。
測試程序1
l 在elipse IDE中調試運行教材585頁程序13-1,結合程序運行結果理解程序;
l 將所生成的JAR文件移到另一個不一樣的目錄中,再運行該歸檔文件,以便確認程序是從JAR文件中,而不是從當前目錄中讀取的資源。
l 掌握建立JAR文件的方法;

ResourceTest
 
package resource;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
/**
 * @version 1.41 2015-06-12
 * @author Cay Horstmann
 */
public class ResourceTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> {
         JFrame frame = new ResourceTestFrame();
         frame.setTitle("ResourceTest");
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);//可視化
      });
   }
}
/**
 * A frame that loads image and text resources.
 */
class ResourceTestFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 300;
   public ResourceTestFrame()
   {
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      URL aboutURL = getClass().getResource("about.gif");   //肯定目標文件,加載資源代碼
      Image img = new ImageIcon(aboutURL).getImage();    //設置圖標
      setIconImage(img);
      JTextArea textArea = new JTextArea();
      InputStream stream = getClass().getResourceAsStream("about.txt");    //讀取文件
      try (Scanner in = new Scanner(stream, "UTF-8"))
      {
         while (in.hasNext())
            textArea.append(in.nextLine() + "\n");
      }
      add(textArea);  //添加到文件區域
   }
}

  

實驗輸出結果截圖爲:

 

 

 

 

 

 

 

 

測試程序2:

l 在elipse IDE中調試運行ThreadTest,結合程序運行結果理解程序;

l 掌握線程概念;

l 掌握用Thread的擴展類實現線程的方法;

l 利用Runnable接口改造程序,掌握用Runnable接口建立線程的方法。 

class Lefthand extends Thread { 
   public void run()
   {
       for(int i=0;i<=5;i++)
       {  System.out.println("You are Students!");
           try{   sleep(500);   }
           catch(InterruptedException e)
           { System.out.println("Lefthand error.");}    
       } 
  } 
}
class Righthand extends Thread {
    public void run()
    {
         for(int i=0;i<=5;i++)
         {   System.out.println("I am a Teacher!");
             try{  sleep(300);  }
             catch(InterruptedException e)
             { System.out.println("Righthand error.");}
         }
    }
}
public class ThreadTest 
{
     static Lefthand left;
     static Righthand right;
     public static void main(String[] args)
     {     left=new Lefthand();
           right=new Righthand();
           left.start();
           right.start();
     }
}

  使用Runable接口改造後的程序以下:

class Lefthand implements Runnable { 
   public void run()
   {
       for(int i=0;i<=5;i++)
       {  System.out.println("You are Students!");
           try{   Thread.sleep(500);//休眠500毫秒
           }
           catch(InterruptedException e)
           { System.out.println("Lefthand error.");}    
       } 
  }
   
 }
class Righthand implements Runnable {
    public void run()
    {
         for(int i=0;i<=5;i++)
         {   System.out.println("I am a Teacher!");
             try{  Thread.sleep(300);//休眠300毫秒
             }
             catch(InterruptedException e)
             { System.out.println("Righthand error.");}
         }
    }
   
 }
public class ThreadTest 
{
     public static void main(String[] args)
     {   

       Righthand righthand = new Righthand();//新建一個righthand對象
        Lefthand lefthand = new Lefthand();//新建一個lefthand對象
         Thread right = new Thread(righthand);//start方法在Thread類中,新建Thread類
         right.start();
           Thread left=new Thread(lefthand);
          left.start();
     }
}

  

 程序運行結果以下:

 

能夠經過兩種方法實現多線程,分別爲:(1)利用Thread類的子類(2)用Runnable()接口實現線程

(1)——定義Thread類的子類並實現用戶線程操做,即 run()方法的實現。

         ——在適當的時候啓動線程。

(2)——首先設計一個實現Runnable接口的類;

         ——而後在類中根據須要重寫run方法;

        ——再建立該類對象,以此對象爲參數創建Thread 類的對象;

        ——調用Thread類對象的start方法啓動線程,將 CPU執行權轉交到run方法。

 

測試程序2:

l 在Elipse環境下調試教材625頁程序14-一、14-2 、14-3,結合程序運行結果理解程序;

l 在Elipse環境下調試教材631頁程序14-4,結合程序運行結果理解程序;

l 對比兩個程序,理解線程的概念和用途;

l 掌握線程建立的兩種技術。

程序以下:

 14-一、14-2 、14-3:

 

import java.awt.*;
import java.util.*;
import javax.swing.*;

/**
 * The component that draws the balls.
 * @version 1.34 2012-01-26
 * @author Cay Horstmann
 */
public class BallComponent extends JPanel
{
   private static final int DEFAULT_WIDTH = 450;
   private static final int DEFAULT_HEIGHT = 350;

   private java.util.List<Ball> balls = new ArrayList<>();

   /**
    * Add a ball to the component.
    * @param b the ball to add
    */
   public void add(Ball b)
   {
      balls.add(b);
   }

   public void paintComponent(Graphics g)
   {
      super.paintComponent(g); // 擦除背景
      Graphics2D g2 = (Graphics2D) g;
      for (Ball b : balls)
      {
         g2.fill(b.getShape());
      }
   }
   
   public Dimension getPreferredSize() 
  { 
       return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

  

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * Shows an animated bouncing ball.
 * @version 1.34 2015-06-21
 * @author Cay Horstmann
 */
public class Bounce
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> {
         JFrame frame = new BounceFrame();
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
      });
   }
}

/**
 * The frame with ball component and buttons.
 */
class BounceFrame extends JFrame
{
   private BallComponent comp;
   public static final int STEPS = 1000;
   public static final int DELAY = 3;

   /**
    * Constructs the frame with the component for showing the bouncing ball and
    * Start and Close buttons
    */
   public BounceFrame()
   {
      setTitle("Bounce");
      comp = new BallComponent();
      add(comp, BorderLayout.CENTER);//使用邊框佈局管理器使其顯示在窗口中心位置
      JPanel buttonPanel = new JPanel();
      addButton(buttonPanel, "Start", event -> addBall());//在窗口添加兩個按鈕
      addButton(buttonPanel, "Close", event -> System.exit(0));
      add(buttonPanel, BorderLayout.SOUTH);//使用邊框佈局管理器使其顯示在窗口下方位置
      pack();
   }

   /**
    * Adds a button to a container.
    * @param c the container
    * @param title the button title
    * @param listener the action listener for the button
    */
   public void addButton(Container c, String title, ActionListener listener)
   {
      JButton button = new JButton(title);
      c.add(button);
      button.addActionListener(listener);
   }

   /**
    * Adds a bouncing ball to the panel and makes it bounce 1,000 times.
    */
   public void addBall()
   {
      try
      {
         Ball ball = new Ball();
         comp.add(ball);

         for (int i = 1; i <= STEPS; i++)
         {
            ball.move(comp.getBounds());
            comp.paint(comp.getGraphics());
            Thread.sleep(DELAY);
         }
      }
      catch (InterruptedException e)
      {
      }
   }
}

  

import java.awt.geom.*;

/**
 * A ball that moves and bounces off the edges of a rectangle
 * @version 1.33 2007-05-17
 * @author Cay Horstmann
 */
public class Ball
{
   private static final int XSIZE = 15;
   private static final int YSIZE = 15;
   private double x = 0;
   private double y = 0;
   private double dx = 1;
   private double dy = 1;

   /**
    * 將球移動到下一個位置,若是球碰到其中一條邊,則反向移動
    */
   public void move(Rectangle2D bounds)
   {
      x += dx;
      y += dy;
      if (x < bounds.getMinX())
      {
         x = bounds.getMinX();
         dx = -dx;
      }
      if (x + XSIZE >= bounds.getMaxX())
      {
         x = bounds.getMaxX() - XSIZE;
         dx = -dx;
      }
      if (y < bounds.getMinY())
      {
         y = bounds.getMinY();
         dy = -dy;
      }
      if (y + YSIZE >= bounds.getMaxY())
      {
         y = bounds.getMaxY() - YSIZE;
         dy = -dy;
      }
   }

   /**
    *獲取球在當前位置的形狀。
    */
   public Ellipse2D getShape()
   {
      return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
   }
}

  

14-4:

package bounceThread;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

/**
 * Shows animated bouncing balls.
 * @version 1.34 2015-06-21
 * @author Cay Horstmann
 */
public class BounceThread
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> {
         JFrame frame = new BounceFrame();
         frame.setTitle("BounceThread");
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
      });
   }
}

/**
 * The frame with panel and buttons.
 */
class BounceFrame extends JFrame
{
   private BallComponent comp;
   public static final int STEPS = 1000;
   public static final int DELAY = 5;


   /**
    * Constructs the frame with the component for showing the bouncing ball and
    * Start and Close buttons
    */
   public BounceFrame()
   {
      comp = new BallComponent();
      add(comp, BorderLayout.CENTER);//使用邊框佈局管理器將它放在窗口的中心位置
      JPanel buttonPanel = new JPanel();
      addButton(buttonPanel, "Start", event -> addBall());
      addButton(buttonPanel, "Close", event -> System.exit(0));
      add(buttonPanel, BorderLayout.SOUTH);//使用邊框佈局管理器將它放在窗口的中心位置
      pack();
   }

   /**
    * Adds a button to a container.
    * @param c the container
    * @param title the button title
    * @param listener the action listener for the button
    */
   public void addButton(Container c, String title, ActionListener listener)
   {
      JButton button = new JButton(title);
      c.add(button);
      button.addActionListener(listener);
   }

   /**
    * Adds a bouncing ball to the canvas and starts a thread to make it bounce
    */
   public void addBall()
   {
      Ball ball = new Ball();
      comp.add(ball);
      Runnable r = () -> { 
         try
         {  
            for (int i = 1; i <= STEPS; i++)
            {
               ball.move(comp.getBounds());
               comp.repaint();
               Thread.sleep(DELAY);
            }
         }
         catch (InterruptedException e)
         {
         }
      };
      Thread t = new Thread(r);
      t.start();//調用Thread類中的start方法
   }
}

  

package bounceThread;

import java.awt.*;
import java.util.*;
import javax.swing.*;

/**
 * The component that draws the balls.
 * @version 1.34 2012-01-26
 * @author Cay Horstmann
 */
public class BallComponent extends JComponent
{
   private static final int DEFAULT_WIDTH = 450;
   private static final int DEFAULT_HEIGHT = 350;

   private java.util.List<Ball> balls = new ArrayList<>();

   /**
    * Add a ball to the panel.
    * @param b the ball to add
    */
   public void add(Ball b)
   {
      balls.add(b);
   }

   public void paintComponent(Graphics g)
   {
      Graphics2D g2 = (Graphics2D) g;
      for (Ball b : balls)
      {
         g2.fill(b.getShape());
      }
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}

package bounceThread;

import java.awt.geom.*;

/**
   A ball that moves and bounces off the edges of a 
   rectangle
 * @version 1.33 2007-05-17
 * @author Cay Horstmann
*/
public class Ball
{
   private static final int XSIZE = 15;
   private static final int YSIZE = 15;
   private double x = 0;
   private double y = 0;
   private double dx = 1;
   private double dy = 1;

   /**
      Moves the ball to the next position, reversing direction
      if it hits one of the edges
   */
   public void move(Rectangle2D bounds)
   {
      x += dx;
      y += dy;
      if (x < bounds.getMinX())
      { 
         x = bounds.getMinX();
         dx = -dx;
      }
      if (x + XSIZE >= bounds.getMaxX())
      {
         x = bounds.getMaxX() - XSIZE; 
         dx = -dx; 
      }
      if (y < bounds.getMinY())
      {
         y = bounds.getMinY(); 
         dy = -dy;
      }
      if (y + YSIZE >= bounds.getMaxY())
      {
         y = bounds.getMaxY() - YSIZE;
         dy = -dy; 
      }
   }

   /**
      Gets the shape of the ball at its current position.
   */
   public Ellipse2D getShape()
   {
      return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
   }
}

  

三:實驗總結    這周主要學習了JAR文件和線程相關的知識,其中線程是程序中一個單一的順序從控制流程。在學習過程當中,瞭解了線程建立的兩種技術:用Thread類的子類建立線程以及用Runnable()接口實現線程。學習用多線程提升實際運行效率,有效增長了cpu的利用時間,提升計算機工做的效率。對於結對編程,預想的很是好,可是在實際操做過程當中,發現本身還存在着很大的問題。在實驗課上老師和學長的幫助後,理解了線程中的優先級。課後本身還會再繼續學習。

相關文章
相關標籤/搜索