java多線程 基礎理解

原文:http://www.cnblogs.com/ckb58/p/7698813.htmlhtml

1、概述

  • 理解多線程先要理解線程,理解線程先要理解進程。

1. 進程

  • 一個正在執行的程序。
  • 每一個進程的執行都有一個執行的順序,順序是一個執行路徑,也叫一個控制單元。

2. 線程

  • 進程中獨立的控制單元稱爲線程。
  • 線程控制進程的執行。
  • 進程中只要有一個線程在執行,進程就不會結束。
  • 一個進程中至少存在一個線程。

3. 多線程

  • Java 虛擬機啓動時,會有一個 java.exe 的執行程序,也就是一個進程。
  • 這個進程中至少存在一個線程負責 java 程序的執行,這個線程的運行代碼存在 main 方法中,這個線程稱之爲主線程。
  • JVM 啓動時除了執行一個主線程,還會啓動負責垃圾回收機制的線程。
  • 在一個進程中有多個線程執行的方式,稱爲多線程。

4. 多線程的意義

  • 多線程能讓程序產生同時運行的效果,能夠提升程序執行的效率。
    • 例如:
      • java.exe 進程執行主程序時,若是程序的代碼很是多,在堆內存中會產生不少對象,而對象調用完後就會變成垃圾。若是垃圾過多的話,可能會致使堆內存出現內存不足的現象,影響程序的運行。這種狀況下,若是隻有一個線程在運行處理的話,程序執行的效率很是低;若是有多個線程在幫助處理的話,程序執行的效率將大大的提升。
        • 例如:垃圾回收機制的線程在幫助進行垃圾回收的話,那堆內存空間的釋放將快不少。

5. CPU 運行的原理

  • PC 上有不少程序「同時」進行,看起來好像是 CPU 「同時」處理全部程序似的,其實在同一時刻,單核的 CPU 只能運行一個程序,看起來「同時」運行的效果,實際上只是 CPU 在多個線程之間作快速切換的動做而已。
  • CPU 執行哪一個程序,或者說是哪一個程序搶到了 CPU 的執行權,哪一個程序就執行,CPU 不會只執行一個線程,執行完一個後,會執行另外一個,或者說是另外一個線程搶走了 CPU 的執行權。至於如何執行是由 CPU 所決定。
  • CPU 執行哪一個程序,是毫無規律的,這是多線程的特性:隨機性。java

    2、建立方式

  • 建立線程的方式:繼承和實現。安全

1. 繼承

  • Java 中已經提供了對線程描述的類 —— Thread。繼承 Thread 類,重寫(覆蓋)run 方法,建立線程。
  • 步驟:
    1. 定義類繼承 Thread;
    2. 重寫(覆蓋)Thread 中的 run 方法;
      • 自定義代碼存儲在 run 方法中,讓線程執行。
    3. 建立自定義類的實例對象;
      • 至關於建立線程。
    4. 實例對象調用線程的 start 方法。
      • 啓動線程,調用 run 方法。
        • 注:若是對象直接調用 run 方法,那麼就只有一個線程在執行,自定義的線程並無啓動。
  • 重寫(覆蓋)run 方法的緣由:Thread 類用於描述線程,類中定義了一個功能,用於存儲線程要執行的代碼,存儲這個功能的就是 run 方法。總而言之,Thread 中的 run 方法用於存儲線程要執行的代碼。
  • 例如:多線程

  • 結果以下函數

  • 注:線程是隨機、交替執行的,每次運行的結果都不一樣this

 

2. 實現

  • 使用繼承 Thread 建立線程的方式有弊端,就是若是類繼承了其它的父類,就沒法使用 Thread 來建立線程,因而便有了經過實現 Runnable 接口來建立線程。實現 Runnable 接口,重寫(覆蓋)run 方法,建立線程。
  • 步驟:
    1. 定義類實現 Runnable;
    2. 重寫(覆蓋)Runnable 中的 run 方法;
      • 自定義代碼存儲在 run 方法中,讓線程執行。
    3. 經過 Thread 類建立線程對象。
    4. 將 Runnable 的子類對象做爲實際參數傳遞給 Thread 的構造函數;
      • 將 Runnable 的子類對象傳遞給 Thread 的構造函數的緣由:
        • 自定義的 run 方法所屬的對象是 Runnable 的子類對象,要讓線程去指定對象的 run 方法,就必須明確 run 方法所屬的對象。
    5. 調用 Thread 中的 start 方法啓動線程。
      • start 方法會自動調用 Runnable 子類的 run 方法。
  • 好處:避免了單繼承的侷限性。(定義線程時,建議優先使用)
  • 例如:
  • 結果以下:spa

  • 注:線程是隨機、交替執行的,每次運行的結果都不一樣。線程

 

3、區別及狀態

1. 建立方式的區別

  • 繼承:線程代碼存儲在 Thread 子類的 run 方法中。
  • 實現:線程代碼存儲在 Runnable 子類的 run 方法中。

2. 狀態

  • 被建立:等待啓動,調用 start 啓動。
  • 運行狀態:具備執行資格和執行權。
  • 臨時狀態(阻塞):具備執行資格,但沒有執行權。
  • 凍結狀態:遇到 sleep(time) 方法和 wait() 方法時,失去執行資格和執行權;sleep 方法的時間結束或調用 notify() 方法時,得到執行資格,變爲臨時狀態(阻塞)。
  • 消亡狀態:調用 stop() 方法或 run 方法結束。
    • 注:線程從建立狀態到了運行狀態後,再次調用 start() 方法時,已經沒有任何意義,Java 運行時會提示線程狀態異常。
    •  

 

4、安全問題

1. 緣由

  • 當多條語句操做同一個線程的共享數據時,一個線程對多條語句只執行了一部分,沒執行完成時,另外的線程參與執行,會致使共享數據的錯誤異常,也就是線程的安全問題。
  • 總而言之:
    1. 線程的隨機性。
    2. 多個線程訪問出現延遲。
  • 注:線程的安全問題在理想狀態下,通常不容易出現,可是一旦出現線程的安全問題,將會對程序軟件形成很是大的影響。

2. 同步

  • 對於線程的安全問題,在對多條操做共享數據的語句時,只讓一個線程執行完,再讓下個線程去執行,每條線程在執行的過程當中,其它線程都不能夠參與執行。
  • Java 中提供了專業的解決辦法 —— synchronized(同步)。
  • 解決的方式:同步代碼塊和同步函數。(均是使用關鍵字 synchronized 實現)
    1. 同步代碼塊
      • 格式:code

        synchronized(對象){ 
            須要被同步的代碼;
         }
      • 同步之因此能夠解決線程的安全問題,根本緣由在於對象上,對象若是加了同步鎖,持有鎖的線程能夠在同步中執行,沒持有鎖的線程即便獲取 CPU 的執行權,也沒法進入去執行,由於沒有獲取到同步鎖。
      • 例如:htm

同步函數

  • 格式:

    在函數上加 synchronized 修飾符便可。
  • 函數須要被對象所調用,函數就擁有一個所屬對象的引用,就是 this,也就是說同步函數所使用的鎖是 this。
  • 例如:

  • 前提:
    1. 必須有兩個或以上的線程;
    2. 必須是多個線程使用同一個鎖。
  • 利與弊:
    • 利:解決了多線程的安全問題。
    • 弊:多個線程均須要判斷鎖,消耗資源,影響效率。
  • 如何尋找多線程的安全問題?
    1. 明確共享數據;
    2. 明確哪些代碼是多線程運行的代碼;
    3. 明確多線程運行的代碼中哪些語句是操做共享數據的。

3. 靜態函數的同步

  • 同步函數被靜態所修飾後,使用的同步鎖再也不是 this,由於靜態函數中不能夠定義 this,靜態進入內存時,內存中沒有本類對象,但必定存在類所對應的字節碼文件對象。
    • 例如:類名.class(對象的類型是 Class)
  • 靜態函數所使用的同步鎖就是所在類的字節碼文件對象。
    • 類名.class
  • 例如:

 

4. 死鎖

  • 同步中嵌套同步時,有可能出現死鎖現象。
  • 例如:

 

說明:程序卡死,沒法繼續執行。

5、通訊

  • 多個線程操做同一個資源,可是操做的動做不相同,就是線程間通訊。
  • 同步操做同一個資源
    • 例如:

 

  • 問題點:
    1. wait()、notify()、notifyAll() 用來操做線程的,爲何是定義在 Object 類中呢?
      1. 這些方法存在於同步中;
      2. 使用這些方法時必需要有標識所屬的同步鎖;
        • 例如:同一個鎖上 wait 的線程,只能被同一個鎖上的 notify 喚醒。
      3. 鎖能夠是任意的對象,任意對象調用的方法必定要定義在 Object 類中。
    2. wait() 和 sleep() 有什麼區別呢?
      • wait():釋放 CPU 的執行權,釋放同步鎖。
      • sleep():釋放 CPU 的執行權,不釋放同步鎖。
    3. 爲何要定義 notifyAll()?
      • 須要喚醒對方線程時,若是隻用 notify(),容易出現只喚醒本方線程的狀況,會致使程序中全部線程都處於等待狀態。

JDK 5 及以上版本中提供了多線程同步鎖的升級解決方案

  • 將 synchronized(同步)替換成 Lock,將 Object 類中的 wait()、notify()、notifyAll() 替換成 Condition 對象。
  • Condition 對象可經過 Lock(鎖)進行獲取,而且支持多個相關的 Condition 對象。
  • 例如:

  •  

 

6、線程中止

  • JDK 5 以前,中止線程用 stop() 方法,JDK 5 及以上版本中,stop() 方法已過期。
    • 線程中止的辦法就是讓 run() 方法結束。
      • 啓動多線程運行通常都是使用循環結構的代碼,只需控制循環的條件,就可讓 run() 方法結束,也就是線程中止。
        • 例如:

 

  • 說明:只要在主函數或者其它線程中,對標記 flag 賦值 false,就可讓 run() 方法結束,線程中止。

特殊狀況:當線程處於凍結狀態時,沒法讀取到 run() 方法中的代碼,線程就沒法中止。

  • 須要對線程的凍結狀態進行清除,強制讓線程恢復運行,Thread 類中提供了 interrupt();
  • 例如:

 

7、什麼狀況須要多線程?

  • 某些代碼須要同時執行時,可用單獨的線程封裝,多線程運行執行。
  • 例如

8、拓展

  • join();
    • 臨時加入線程去執行:
      • 當 A 線程執行到了 B 線程的 join(); 方法時,A 線程等待,B 線程執行完後,A 線程才繼續執行(此時的 B 線程與其它線程交替執行)。
  • setPriority();
    • 設置優先級:
      • MIN_PRIORITY:最低優先級 1
      • MAX_PRIORITY:最高優先級 10
      • NORM_PRIORITY:默認優先級
  • yield();
    • 能夠暫停當前線程,讓其它線程執行。
相關文章
相關標籤/搜索