Java筆記:多線程
一、意義
使用多線程的目的是為了提高CPU資源的利用效率。在單線程應用中程序必須等待當前任務的完成才能繼續執行下一項任務,CPU在等待的時間內就閑置了,多線程的使用可減少閑置時間。
二、主線程
當Java程序啟動時,會立即開始運行主線程。其他所有的線程都是從主線程產生的,主線程必須是最後才結束執行的線程,因為它需要執行各種關閉動作。可以通過currentThread靜態方法獲取主線程的引用。
class Solution { public static void main(String[] args) { Thread t = Thread.currentThread(); System.out.println("Current thread: " + t);//Thread[main,5,main] try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println("Current thread interrupted"); } } }
默認情況下,主線程的名字是main,優先級是5,main也是主線程所屬的線程組的名字。線程組是將線程作為一個整體來控制狀態的數據結構。
三、創建線程
可通過繼承Thread類或實現Runnable接口來創建線程。
實現Runnable接口創建線程時,只需要實現run方法,run方法定義線程的代碼,線程隨run方法結束而結束。在創建了新線程之後,只有調用start方法線程才會運行,本質上start方法執行對run方法的調用。但是Runnable接口只定義了run方法,執行run類的代碼必須借助Thread實例。
擴展Thread類創建線程時, 必須重寫run方法或定義Runnable接口實例。
class ThreadA extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(getName() + " interrupted"); } } ThreadA(String name) { super(name); start(); } } class ThreadB implements Runnable { private Thread t; @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(t.getName() + " interrupted"); } } ThreadB(String name) { t = new Thread(this, name); t.start(); } } class Solution { public static void main(String[] args) { ThreadA tA = new ThreadA("A"); ThreadB tB = new ThreadB("B"); } }
四、等待線程
當某個線程需要耗費大量時間運算,而其他線程又需要等待該線程結束後才能繼續獲取數據。這種情況下可直接調用該線程的join方法,程序會停留於調用join方法處直至調用該方法的線程結束才會繼續執行後面的代碼。
class MyThread extends Thread { @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println(i); Thread.sleep(500); } } catch (InterruptedException exc) { System.out.println(getName() + " interrupted"); } } MyThread(String name) { super(name); } } class Solution { public static void main(String[] args) { MyThread tA = new MyThread("thread A"); MyThread tB = new MyThread("thread B"); MyThread tC = new MyThread("thread C"); try { tA.start(); tA.join();//停留直至tA結束 tB.start(); tB.join(1000);//停留1秒後無論tB是否結束都繼續執行後面的代碼 tC.start(); tC.join();//停留直至tC結束 } catch (InterruptedException exc) { System.out.println("Thread interrupted"); } System.out.println("Thread A is alive " + tA.isAlive()); System.out.println("Thread B is alive " + tB.isAlive()); System.out.println("Thread C is alive " + tC.isAlive()); } }
五、優先級
Java為線程指定了優先級,優先級決定了相對於其他線程會如何處理某個線程。優先級是整數,但絕對值沒有意義。線程的優先級必須在MIN_PRIORITY到MAX_PRIORITY之間,在線程中是作為靜態常量定義的。優先級決定何時從一個運行的線程切換到下一個,稱為上下文切換。發生規則如下:
- 線程自願放棄控制。線程顯式放棄控制權、休眠或再I/O之前阻塞,都會導致這種情況的出現。發生這種情況時,會檢查其他線程,並且即將運行的線程中優先級最高的會獲得CPU資源。
- 線程被優先級更高的線程取代。沒有放棄控制權的低優先級線程無論在做什麽,都會被高優先級的線程取代。只要高優先級線程運行,就會取代低優先級線程,稱為搶占式多任務處理。
具有相同優先級的線程競爭CPU資源時,Windows中會以循環的方式自動獲得CPU資源,其他操作系統中優先級相等的線程必須主動放棄控制權其他線程才能運行。理論上優先級更高的線程會獲得更多的CPU時間,具有相同優先級的線程應當得到相同的CPU時間,但不同環境的多任務方式不同,為了安全起見,具有相同優先級的線程應當時不時放棄控制權,以確保所有線程在非搶占式操作系統中有機會運行。
六、同步
多線程使程序可以進行異步行為,所以必須提供一種在需要時強制同步的方法。例如當兩個線程進行通信並共享某個復雜的數據結構,當一個線程向數據結構中寫入數據時,必須阻止其他線程向數據結構中寫入數據,否則可能會發生沖突。
同步的關鍵是監視器,監視器是用作互斥鎖的對象。在給定時刻只有一個線程可以擁有監視器,一旦線程進入監視器,也就是取得鎖,其他所有線程就必須等待,直至該線程退出監視器,其他所有嘗試進入加鎖監視器的線程都會被掛起。Java中每個類都有自己隱式的監視器,每當對象的同步方法被調用時,線程就會自動進入對象的隱式監視器。
class Callme { synchronized void call(String msg) {//同步方法 System.out.print("[" + msg); try { Thread.sleep(1000); System.out.println("]"); } catch (InterruptedException exc) { System.out.println("Callme interrupted"); } } } class Caller extends Thread { private String msg; private Callme target; Caller(Callme target, String msg) { this.target = target; this.msg = msg; start(); } @Override public void run() { target.call(msg); } } class Solution { public static void main(String[] args) { Callme target = new Callme(); Caller obA = new Caller(target, "Caller A"); Caller obB = new Caller(target, "Caller B"); Caller obC = new Caller(target, "Caller C"); try { obA.join(); obB.join(); obC.join(); } catch (InterruptedException exc) { System.out.println("Caller interrupted"); } } }
但是假設某個類並沒有對多線程進行設計,即類中沒有同步方法。由於我們並不是該類的創建者,無法訪問其源代碼,也就無法使用上述方法為類中的方法添加synchronized修飾符。那麽可以將需要同步的部分放到synchronized代碼塊中。
class Caller extends Thread { private String msg; private Callme target; Caller(Callme target, String msg) { this.target = target; this.msg = msg; start(); } @Override public void run() { synchronized (target) {//同步代碼塊 target.call(msg); } } }
Java筆記:多線程