@Java | Thread & synchronized - [ 多線程 基本使用]

本文及後續相關文章梳理一下關於多線程和同步鎖的知識,平時只是應用層面的瞭解,因爲最近面試老是問一些原理性的知識,雖然說比較反感這種理論派,可是爲了生計也必須掌握一番。(PS:並非說掌握原理很差,可是封裝就是爲了更好的應用,我的感受不必爲了學習而學習,比較傾向於行動派,能將原理應用到實際纔算參透,本文也僅僅是背書而已)

知識點

  • 進程:進程就是一段程序的執行過程,指在系統中能獨立運行並做爲資源分配的基本單位,它是由一組機器指令、數據和堆棧等組成的,是一個能獨立運行的活動實體。
  • 線程:線程是進程中的一個實體,做爲系統調度和分派的基本單位。Linux下的線程看做輕量級進程。
線程和進程同樣分爲五個階段:建立、就緒、運行、阻塞、終止
多進程是指操做系統能同時運行多個任務(程序)
多線程是指在同一程序中有多個順序流在執行

多線程使用說明

如何建立線程

  • 實現Runnable接口
  • 繼承Thread類
  • 經過Callable和Future建立線程

1. 經過實現Runnable接口建立並運行線程

- 實現Runnable接口java

Public class A implements Runnable {
    public void run () {    // 必須實現run方法
        // 線程執行的業務代碼
    }
}
上面實現了 Runnable接口並經過 run方法包裝了須要經過線程執行的代碼

- 運行線程
經過實現了Runnable接口的類的實例建立線程對象(Thread)來運行線程,經常使用的Thread構造方法:程序員

Thread(Runnable threadOb,String threadName);
其中 threadOb 是一個實現 Runnable接口的類的 實例threadName指定線程的名字

調用線程類中的start()方法運行線程 new Thread(threadOb,threadName).start();(可建立多個線程對象運行同一個Runnable接口實例的run方法,實現資源共享面試

PS: start()方法的調用後並非當即執行多線程代碼,而是使得該線程變爲 可運行態(Runnable),等待CPU分配調度執行

2. 繼承Thread類建立並運行線程

- 繼承Thread類安全

public class A extends Thread {
    @Override
    public void run() {    // 重寫run方法
        // 線程執行的業務代碼
    }
}
上面繼承了 Runnable接口並經過重寫 run方法包裝了須要經過線程執行的代碼,經過源碼能夠看到 Thread類也是實現了 Runnable接口的,因此本質上和上一種方式無太大區別,不一樣的是Thread類不適合 共享資源線程實現

- 運行線程多線程

一樣是調用線程類中的start()方法運行線程,此時線程類爲繼承Thread的類ide

3. 經過Callable和Future建立並運行線程

- 實現Callable接口學習

public class A implements Callable<T> {
    @Override  
    public T call() throws Exception  // 實現call()方法
    {  
        //  線程執行的業務代碼
    }  
}
建立 Callable接口的實現類 (經過泛型制定線程執行結束後的返回值類型),並實現 call()方法,該 call()方法將做爲線程執行體,而且有 返回值(返回值類型爲 Callable接口泛型制定的類型)

- 使用FutureTask類來包裝Callable對象操作系統

FutureTask<T> ft = new FutureTask<>(callableObj);
其中 callableObjCallable實現類的實例,使用 FutureTask類來包裝 Callable對象,該 FutureTask對象封裝了該 Callable對象call()方法的返回值

- 運行線程線程

經過FutureTask類的實例建立線程對象(Thread)來運行線程,此時應用的Thread`構造方法:code

Thread(FutureTask futureObj,String threadName);
其中 futureObj 是一個 FutureTask 類的 實例threadName指定線程的名字

調用線程類中的start()方法運行線程 new Thread(threadOb,threadName).start();
調用FutureTask對象的get()方法來得到子線程執行結束後的返回值

使用須知

  1. 多線程執行的時候是無序的,即誰先獲取到CPU資源就能夠先執行,隨機性比較大
  2. 若是start()方法重複調用,會出現java.lang.IllegalThreadStateException異常
  3. 直接繼承Thread類和實現接口方式建立線程的區別

    • 直接繼承Thread類方便快捷,適合相對單一無序的多線程執行
    • 實現接口方式適合多個相同的程序代碼的線程去處理同一個資源
    • 實現接口方式能夠避免java中的單繼承的限制
    • 實現接口方式增長程序的健壯性,代碼能夠被多個線程共享,代碼和數據獨立
    • 線程池只能放入實現Runablecallable類線程,不能直接放入繼承Thread的類
  4. Java程序運行首先會啓動一個JVM進程,而後至少啓動兩個線程,即main線程垃圾收集線程

Thread經常使用方法說明

  1. sleep(long millis): 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),可用TimeUnit.MILLISECONDS.sleep方法替換
  2. join(): 等待調用join()的線程終止

    • 使用方式

      `Thread t = new AThread(); t.start(); t.join();`
      join()的做用是將線程加入到 當前線程中,只有執行完join()調用線程才能執行後面的代碼
    • 使用場景
      正常狀況下主線程不依賴子線程執行完成而結束,當主線程須要在子線程完成以後再結束時,可以使用此方法
  3. yield(): 暫停當前正在執行的線程對象,並執行其餘線程

    • 使用說明
      yield()只是將線程從運行狀態轉到可運行狀態(start()方法執行後的狀態),不會致使線程轉到等待/睡眠/阻塞狀態
    • 使用場景
      yield()應該作的是讓當前運行線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行機會。所以,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。可是,實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中

      PS: yield()方法執行時,當前線程仍處在可運行狀態,因此,不可能讓出較低優先級的線程些時得到 CPU佔有權。在一個運行系統中,若是較高優先級的線程沒有調用 sleep方法,又沒有受到 I\O 阻塞,那麼,較低優先級線程只能等待全部較高優先級的線程運行結束,纔有機會運行
  4. setPriority(): 更改線程的優先級,優先級高的線程會得到較多的運行機會
    優先級靜態常量MIN_PRIORITY=1,NORM_PRIORITY=5,MAX_PRIORITY=10

    • 使用方式

      Thread t1 = new Thread("t1");
      Thread t2 = new Thread("t2");
      t1.setPriority(Thread.MAX_PRIORITY);
      t2.setPriority(Thread.MIN_PRIORITY);
    • 使用說明
      Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。
      每一個線程都有默認的優先級。主線程的默認優先級爲Thread.NORM_PRIORITY。
      線程的優先級有繼承關係,好比A線程中建立了B線程,那麼B將和A具備相同的優先級。
      JVM提供了10個線程優先級,但與常見的操做系統都不能很好的映射。若是但願程序能移植到各個操做系統中,應該僅僅使用Thread類有如下三個靜態常量做爲優先級,這樣能保證一樣的優先級採用了一樣的調度方式
  5. interrupt(): 將線程對象的中斷標識設成true

    • 使用說明

      • 中斷只是一種協做機制,Java沒有給中斷增長任何語法,中斷的過程徹底須要程序員本身實現。若要中斷一個線程,你須要手動調用該線程的interrupted方法,該方法也僅僅是將線程對象的中斷標識設成true;接着你須要本身寫代碼不斷地檢測當前線程的標識位;若是爲true,表示別的線程要求這條線程中斷,此時究竟該作什麼須要你本身寫代碼實現
      • 每一個線程對象中都有一個標識,用於表示線程是否被中斷;該標識位爲true表示中斷,爲false表示未中斷
      • 經過調用線程對象的interrupt方法將該線程的標識位設爲true;能夠在別的線程中調用,也能夠在本身的線程中調用
    • 使用方式

      • 在調用阻塞方法時正確處理InterruptedException異常
      • 設置中斷監聽(另外一種方式)

        Thread t1 = new Thread( new Runnable(){
               public void run(){
                   // 若未發生中斷,就正常執行任務
                   while(!Thread.currentThread.isInterrupted()){
                       // 正常任務代碼……
                   }
           
                   // 中斷的處理代碼……
                   doSomething();
               }
           } ).start();
      • 觸發中斷

        t1.interrupt();
    • interrupt方法使用參考連接
      大閒人柴毛毛
  6. wait(): 主動釋放對象鎖,同時本線程休眠,直到有其它線程調用對象的notify()喚醒該線程,從新獲取對象鎖並執行(wait()方法屬於Object中的方法,並不屬於Thread類)
  7. notify(): 喚醒調用notify()對象的線程,notify()調用後,並非立刻就釋放對象鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()對象鎖的線程中隨機選取一線程,賦予其對象鎖,喚醒線程,繼續執行

Thread幾個方法比較

1.sleep()yield()的區別

  • sleep()使當前線程進入停滯狀態,因此執行sleep()的線程在指定的時間內確定不會被執行;yield()只是使當前線程從新回到可執行狀態,因此執行yield()的線程有可能在進入到可執行狀態後立刻又被執行
  • sleep方法使當前運行中的線程睡眼一段時間,進入不可運行狀態,這段時間的長短是由程序設定的,yield方法使當前線程讓出CPU佔有權,但讓出的時間是不可設定的。實際上,yield()方法對應了以下操做:先檢測當前是否有相同優先級的線程處於同可運行狀態,若有,則把CPU的佔有權交給此線程,不然,繼續運行原來的線程。因此yield()方法稱爲'退讓',它把運行機會讓給了同等優先級的其餘線程
  • sleep方法容許較低優先級的線程得到運行機會,但yield()方法執行時,當前線程仍處在可運行狀態,因此,不可能讓出較低優先級的線程些時得到CPU佔有權。在一個運行系統中,若是較高優先級的線程沒有調用 sleep方法,又沒有受到IO阻塞,那麼,較低優先級線程只能等待全部較高優先級的線程運行結束,纔有機會運行

2.wait()sleep()區別

共同點:

  • 他們都是在多線程的環境下,均可以在程序的調用處阻塞指定的毫秒數,並返回
  • wait()和sleep()均可以經過interrupt()方法 打斷線程的暫停狀態,從而使線程馬上拋出InterruptedException
  • 若是線程A但願當即結束線程B,則能夠對線程B對應的Thread實例調用interrupt方法。若是此刻線程B正在wait/sleep /join,則線程B會馬上拋出InterruptedException,在catch() {} 中直接return便可安全地結束線程。
須要注意的是, InterruptedException是線程本身從內部拋出的,並非 interrupt()方法拋出的。對某一線程調用 interrupt()時,若是該線程正在執行普通的代碼,那麼該線程根本就不會拋出 InterruptedException。可是,一旦該線程進入到 wait()/ sleep()/ join()後,就會馬上拋出 InterruptedException

不一樣點:

  • sleep(),yield()等是Thread類的方法
  • wait()和notify()等是Object的方法
  • 每一個對象都有一個鎖來控制同步訪問。Synchronized關鍵字能夠和對象的鎖交互,來實現線程的同步。sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程可使用同步控制塊或者方法
  • wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用

簡單來講: 

  • sleep()睡眠時,保持對象鎖,仍然佔有該鎖;
  • 而wait()睡眠時,釋放對象鎖。
  • wait()和sleep()均可以經過interrupt()方法打斷線程的暫停狀態,從而使線程馬上拋出InterruptedException(但不建議使用該方法)

sleep()方法

  • sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CPU的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留必定時間給其餘線程執行的機會;
  • sleep()是Thread類的Static的方法;所以他不能改變對象的機鎖,因此當在一個Synchronized塊中調用Sleep()方法是,線程雖然休眠了,可是對象的機鎖並無被釋放,其餘線程沒法訪問這個對象(即便休眠也持有對象鎖
  • 在sleep()休眠時間期滿後,該線程不必定會當即執行,這是由於其它線程可能正在運行並且沒有被調度爲放棄執行,除非此線程具備更高的優先級

wait()方法

  • wait()方法是Object類裏的方法;當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時失去(釋放)了對象的機鎖(暫時失去機鎖,wait(long timeout)超時時間到後還須要返還對象鎖);其餘線程能夠訪問;
  • wait()使用notify或者notifyAlll或者指定睡眠時間來喚醒當前等待池中的線程。
  • wait()必須放在synchronized block中,不然會在程序runtime時扔出java.lang.IllegalMonitorStateException異常。
相關文章
相關標籤/搜索