Java多線程編程核心技術-第1章-Java多線程技能-讀書筆記

第 1 章 Java 多線程技能

本章主要內容

線程的啓動
如何使線程暫停
如何使線程停止
線程的優先級
線程安全相關的問題

1.1 進程和多線程的概念及線程的優點

  進程是操作系統結構的基礎;是一次程序的執行;是一個程序及其數據在處理機上順序執行時所發生的活動;是程序在一個數據集合上運行的過程,它是系統進行資源分配和調度的一個獨立單位。進程是操作系統管理的基本運行單元。

  那什麼是線程呢?線程可以理解成是在進程中獨立運行的子任務。

  使用多線程技術後,可以在同一時間內運行更多不同種類的任務。

1.2 使用多線程

  一個進程正在運行時至少會有 1 個線程在運行。

1.2.1 繼承 Thread 類

  實現多線程編程的方式主要有兩種:一種是繼承 Thread 類,另一種是實現 Runnable 接口。

  Thread 類實現了 Runnable 接口,它們之間具有多態關係。

  線程是一個子任務,CPU 以不確定的方式,或者說是以隨機的時間來調用線程中的 run 方法。

  Thread.java 類中的 start() 方法通知“線程規劃器”此線程已經準備就緒,等待調用線程對象的 run() 方法。這個過程其實就是讓系統安排一個時間來調用 Thread 中的 run() 方法,也就是使線程得到運行,啓動線程,具有異步執行的效果。如果調用代碼 thread.run() 就不是異步執行了,而是同步,那麼此線程對象並不交給“線程規劃器”來進行處理,而是由 main 主線程來調用 run() 方法,也就是必須等 run() 方法中的代碼執行完後纔可以執行後面的代碼。

  執行 start() 方法的順序不代表線程啓動的順序。

1.2.2 實現 Runnable 接口

  使用繼承 Thread 類的方式來開發多線程應用程序在設計上是有侷限性的,因爲 Java 是單根繼承,不支持多繼承,所以爲了改變這種限制,可以使用實現 Runnable 接口的方式來實現多線程技術。 Thread.java 類也實現了 Runnbale 接口。

1.2.3 實例變量與線程安全

  自定義線程類中的實例變量針對其他線程可以有共享與不共享之分。

  每個線程都有各自的變量,自己改變自己變量的值,這種情況就是變量不共享。

  共享數據的情況就是多個線程可以訪問同一個變量。

  非線程安全主要是指多個線程對同一個對象中的同一個實例變量進行操作時會出現值被更改、值不同步的情況,進而影響程序的執行流程。

  可以使用 synchronized 關鍵字解決非線程安全問題。

1.2.4 留意 i— 與 System.out.println() 的異常

    System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());

  雖然 println() 方法在內部是同步的,但是 i— 的操作卻是在進入 println() 之前發生的,所以有發生非線程安全問題的概率。所以,爲了防止發生非線程安全問題,還是應繼續使用同步方法。

1.3 currentThread() 方法

  currentThread() 方法可返回代碼段正在被哪個線程調用的信息。

1.4 isAlive() 方法

  方法 isAlive() 的功能是判斷當前的線程是否處於活動狀態。什麼是活動狀態呢?活動狀態就是線程已經啓動且尚未終止。線程處於正在運行或準備開始運行的狀態,就認爲線程是“存活”的。

1.5 sleep() 方法

  方法 sleep() 的作用是在指定的毫秒數內讓當前“正在執行的線程”休眠(暫停執行)。這個“正在執行的線程”是指 this.currentThread() 返回的線程。

1.6 getId() 方法

  getId() 方法的作用是取得線程的唯一標識。

1.7 停止線程

  停止一個線程意味着在線程處理完任務之前停掉正在做的操作,也就是放棄當前的操作。停止一個線程可以使用 Thread.stop() 方法,但最好不用它。雖然它確實可以停止一個正在運行的線程,但是這個方法是不安全的(unsafe),而且是已被棄用作廢的(deprecated),在將來的 Java 版本中,這個方法將不可用或不被支持。

  大多數停止一個線程的操作使用 Thread.interrupt() 方法,儘管方法的名稱是“停止,中止”的意思,但這個方法不會中止一個正在運行的線程,還需要加入一個判斷纔可以完成線程的停止。

  在 Java 中有以下 3 種方法可以終止正在運行的線程:
1)使用退出標誌,使線程正常退出,也就是當 run 方法完成後線程終止。
2)使用 stop 方法強行終止線程,但是不推薦使用這個方法,因爲 stop 和 suspend 及 resume 一樣,都是作廢過期的方法,使用它們可能產生不可預料的結果。
3)使用 interrupt 方法中斷線程。

1.7.1 停止不了的線程

  調用 interrupt() 方法僅僅是在當前線程中打了一個停止的標記,並不是真的停止線程。 

1.7.2 判斷線程是否是停止狀態

  在 Java 的 SDK 中,Thread.java 類裏提供了兩種方法來判斷線程的狀態是不是停止的。
1)this.interrupted():測試當前線程是否已經中斷。
2)this.isInterrupted():測試線程是否已經中斷。

  interrupted() 方法的解釋:測試當前線程是否已經中斷。

  官方文檔對 interrupted 方法的解釋:測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態之後,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。interrupted() 方法具有清除狀態的功能。

  方法 isInterrupted() 並未清除狀態標誌。

  this.interrupted():測試當前線程是否已經是中斷狀態,執行後具有將狀態清除爲 false 的功能。this.isInterrupted():測試線程 Thread 對象是否已經是中斷狀態,但不清除狀態標誌。

1.7.3 能停止的線程—-異常法

  使用 throw new InterruptedException() 方法,在線程中斷之後停止中斷之後的代碼運行。

1.7.4 在沉睡中停止

  如果在 sleep 狀態下停止某一線程,會進入 catch 語句,並且清除停止狀態值,使之變成 false。

1.7.5 能停止的線程—-暴力停止

  使用 stop() 方法停止線程則是非常暴力的。

1.7.6 方法 stop() 與 java.lang.ThreadDeath 異常

  調用 stop() 方法時會拋出 java.lang.ThreadDeath 異常,但在通常的情況下,此一場不需要顯示地捕獲。

  方法 stop() 已經被作廢,因爲如果強制讓線程停止則有可能使一些清理性的工作得不到完成。另外一個情況就是對鎖定的對象進行了“解鎖”,導致數據得不到同步的處理,出現數據不一致的問題。

1.7.7 釋放鎖的不良後果

  使用 stop() 釋放鎖將會給數據造成不一致性的結果。如果出現這樣的情況,程序處理的數據就有可能遭到破壞,最終導致程序執行的流程錯誤,一定要特別注意。

  由於 stop() 方法已經在 JDK 中被標明使“作廢/過期”的方法,顯然它在功能上具有缺陷,所以不建議在程序中使用 stop() 方法。

1.7.8 使用 return 停止線程

  將方法 interrupted() 與 return 結合使用也能實現停止線程的效果。

  在線程中斷之後,使用 return 使得無法運行中斷之後的代碼。

  不過還是建議使用“拋異常”的方法來實現線程的停止,因爲在 catch 塊中還可以將異常向上拋,使線程停止的事件得以傳播。

1.8 暫停線程

  暫停線程意味着此線程還可以恢復運行。在 Java 多線程中,可以使用 suspend() 方法暫停線程,使用 resume() 方法恢復線程的執行。

1.8.1 suspend 與 resume 方法的使用

  suspend() 方法會暫停線程,resume() 方法hi恢復線程的執行。

1.8.2 suspend 與 resume 方法的缺點—-獨佔

  在使用 suspend 與 resume 方法時,如果使用不當,極易造成公共的同步對象的獨佔,使得其他線程無法訪問公共同步對象。

1.8.3 suspend 與 resume 方法的缺點—-不同步

  在使用 suspend 與 resume 方法時也容易出現因爲線程的暫停而導致數據不同步的情況。

1.9 yield 方法

  yield() 方法的作用使放棄當前的 CPU 資源,將它讓給其他的任務去佔用 CPU 執行事件。但放棄的時間不確定,有可能剛剛放棄,馬上又獲得 CPU 時間片。

1.10 線程的優先級

  在操作系統中,線程可以劃分優先級,優先級較高的線程得到的 CPU 資源較多,也就是 CPU 優先執行優先級較高的線程對象中的任務。

  設置線程優先級有助於幫“線程規劃器”確定在下一次選擇哪一個線程來優先執行。

  設置線程的優先級使用 setPriority() 方法。

  在 Java 中,線程的優先級分爲 1 ~ 10 這 10 個等級,如果小於 1 或大於 10,則 JDK 拋出異常 throw new IllegalArgumentException() 。

  JDK 中使用 3 個常量來預置定義優先級的值:

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

1.10.1 線程優先級的繼承特性

  在 Java 中,線程的優先級具有繼承性。比如 A 線程啓動 B 線程,則 B 線程的優先級與 A 是一樣的。

1.10.2 優先級具有規則性

  高優先級的線程總是大部分先執行完,但不代表高優先級的線程全部先執行完。線程的喲先機與代碼執行順序無關,線程的優先級具有一定的規則性,也就是 CPU 儘量將執行資源讓給優先級比較高的線程。

1.10.3 優先級具有隨機性

  線程的優先級還具有“隨機性”,也就是優先級較高的線程不一定每一次都先執行完。

  不要把線程的優先級與運行結果的順序作爲衡量的標準,優先級較高的線程並不一定每一次都先執行完 run() 方法中的任務,也就是說,線程優先級與打印順序無關,不要將這兩者的關係相關聯,它們的關係具有不確定性和隨機性。

1.10.4 看誰運行得快

  優先級高的運行的快。

1.11 守護線程

  在 Java 線程中有兩種線程,一種是用戶線程,另一種是守護現線程。

  守護線程是一種特殊的線程,它的特性是“陪伴”的含義,當進程中不存在非守護線程了,則守護線程自動銷燬。典型的守護線程就是垃圾回收線程,當進行中沒有非守護線程了,則垃圾回收線程也就沒有存在的必要了,自動銷燬。

  setDaemon(true)將線程設置爲守護線程。

1.12 本章小結

  本章介紹了 Thread 類的 API,在使用這些 API 的過程中,會出現一些意想不到的情況,其實這也是多線程具有不可預知性的一個體現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章