1.線程的概念:線程(thread)是指一個任務從頭至尾的執行流,線程提供一個運行任務的機制,對於java而言,一個程序中可以併發的執行多個線程,這些線程可以在多處理器系統上同時運行。當程序作爲一個應用程序運行時,java解釋器爲main()方法啓動一個線程。
2.並行與併發:
(1)併發:在單處理器系統中,多個線程共享CPU時間,而操作系統負責調度及分配資源給它們。
(2)並行:在多處理器系統中,多個處理器可以同時運行多個線程,這些線程在同一時間可以同時運行,而不同於併發,只能多個線程共享CPU時間,同一時間只能運行一個線程。
3.線程的創建:
(1)基礎概念:java中每個任務就是一個可運行對象,爲了創建任務,必須首先定義任務類,任務類必須實現Runnable接口。而線程本質上講就是便於任務執行的對象。一個線程的執行過程就是一個任務類中run()方法的執行到結束。
(2)通過Runnable接口創建線程:
a.定義一個任務類實現Runnable接口,實現Runnable接口中的run()方法(run()方法告知系統線程該如何運行),run()方法中定義具體的任務代碼或處理邏輯。
b.定義了任務類後,爲任務類創建一個任務對象。
c.任務必須在線程中執行,創建一個Tread類的對象,將前面創建的實現了Runnable接口的任務類對象作爲參數傳遞給Tread類的構造方法。
d.調用Tread類對象的start()方法,啓動一個線程。它會導致任務的run()方法被執行,當run()方法執行完畢,則線程就終止。
實例代碼:
1 package com.muzeet.mutithread; 2 3 //每個任務都是Runable接口的一個實例,任務是可運行對象,線程是便於任務執行的對象。必須創建任務類,重寫run方法定義任務 4 public class ThreadDemo1 implements Runnable { 5 private int countDown = 10; 6 @Override 7 //重寫run方法,定義任務 8 public void run() { 9 while(countDown-- >0) 10 { 11 System.out.println("$" + Thread.currentThread().getName() 12 + "(" + countDown + ")"); 13 } 14 } 15 //調用start方法會啓動一個線程,導致任務中的run方法被調用,run方法執行完畢則線程終止 16 17 public static void main(String[] args) { 18 Runnable demo1 = new ThreadDemo1(); 19 20 Thread thread1 = new Thread(demo1); 21 Thread thread2 = new Thread(demo1); 22 thread1.start(); 23 thread2.start(); 24 25 System.out.println("火箭發射倒計時:"); 26 27 28 } 29 30 }
程序運行結果:
火箭發射倒計時: $Thread- 0 ( 9 ) $Thread- 0 ( 8 ) $Thread- 0 ( 7 ) $Thread- 0 ( 6 ) $Thread- 0 ( 5 ) $Thread- 0 ( 4 ) $Thread- 0 ( 3 ) $Thread- 0 ( 2 ) $Thread- 0 ( 1 ) $Thread- 0 ( 0 ) |
同時運行兩個任務對象:
public static void main(String[] args) { Runnable demo1 = new ThreadDemo1(); Runnable demo2 = new ThreadDemo1(); Thread thread1 = new Thread(demo1); Thread thread2 = new Thread(demo2); thread1.start(); thread2.start(); System.out.println("火箭發射倒計時:"); }
運行結果:
火箭發射倒計時: $Thread- 0 ( 9 ) $Thread- 0 ( 8 ) $Thread- 0 ( 7 ) $Thread- 0 ( 6 ) $Thread- 1 ( 9 ) $Thread- 0 ( 5 ) $Thread- 1 ( 8 ) $Thread- 0 ( 4 ) $Thread- 1 ( 7 ) $Thread- 0 ( 3 ) $Thread- 1 ( 6 ) $Thread- 1 ( 5 ) $Thread- 0 ( 2 ) $Thread- 1 ( 4 ) $Thread- 1 ( 3 ) $Thread- 1 ( 2 ) $Thread- 1 ( 1 ) $Thread- 1 ( 0 ) $Thread- 0 ( 1 ) $Thread- 0 ( 0 ) |
(3)繼承Thread類來創建線程:
a.首先創建一個任務類extends Thread類,因爲Thread類實現了Runnable接口,所以自定義的任務類也實現了Runnable接口,重新run()方法,其中定義具體的任務代碼或處理邏輯。
b.創建一個任務類對象,可以用Thread或者Runnable作爲自定義的變量類型。
c.調用自定義對象的start()方法,啓動一個線程。
示例代碼:
1 package com.muzeet.mutithread; 2 3 //每個任務都是Runable接口的一個實例,任務是可運行對象,線程即可運行對象。必須創建任務類,重寫run方法定義任務 4 public class ExtendFromThread extends Thread { 5 private int countDown = 10; 6 @Override 7 //重寫run方法,定義任務 8 public void run() { 9 while(countDown-- >0) 10 { 11 System.out.println("$" + this.getName() 12 + "(" + countDown + ")"); 13 } 14 } 15 //調用start方法會啓動一個線程,導致任務中的run方法被調用,run方法執行完畢則線程終止 16 17 public static void main(String[] args) { 18 19 ExtendFromThread thread1 = new ExtendFromThread(); 20 ExtendFromThread thread2 = new ExtendFromThread(); 21 thread1.start(); 22 thread2.start(); 23 24 System.out.println("火箭發射倒計時:"); 25 26 27 } 28 29 }
運行結果:
火箭發射倒計時: $Thread- 0 ( 9 ) $Thread- 0 ( 8 ) $Thread- 0 ( 7 ) $Thread- 0 ( 6 ) $Thread- 0 ( 5 ) $Thread- 0 ( 4 ) $Thread- 0 ( 3 ) $Thread- 0 ( 2 ) $Thread- 0 ( 1 ) $Thread- 0 ( 0 ) $Thread- 1 ( 9 ) $Thread- 1 ( 8 ) $Thread- 1 ( 7 ) $Thread- 1 ( 6 ) $Thread- 1 ( 5 ) $Thread- 1 ( 4 ) $Thread- 1 ( 3 ) $Thread- 1 ( 2 ) $Thread- 1 ( 1 ) $Thread- 1 ( 0 ) |
一個線程等待另一個線程結束後再執行:當執行PrintNum這個任務時,打印到數字50時,轉而去執行打印字符C這個任務,知道線程thread4執行完才繼續執行打印數字任務。
1 package com.muzeet.testThread; 2 3 public class PrintNum implements Runnable { 4 5 private int lastNum; 6 7 public PrintNum(int n) 8 { 9 lastNum = n; 10 } 11 12 @Override 13 public void run() { 14 // TODO Auto-generated method stub 15 Thread thread4 = new Thread(new PrintChar('c', 40)); 16 thread4.start(); 17 try { 18 for(int i=1;i<=lastNum;i++) 19 { 20 System.out.println(" " + i); 21 if(i == 50) 22 { 23 24 thread4.join(); 25 26 } 27 } 28 } catch (InterruptedException e) { 29 // TODO Auto-generated catch block 30 e.printStackTrace(); 31 } 32 } 33 34 }
4.兩種方法的比較(轉載)
首先分析兩種方式的輸出結果,同樣是創建了兩個線程,爲什麼結果不一樣呢?
使用實現Runnable接口方式創建線程可以共享同一個目標對象(TreadDemo1 tt=new TreadDemo1();),實現了多個相同線程處理同一份資源。當第一個線程執行完任務後,countDown已經爲0,所以第二個線程就不會輸出。而繼承Thread創建線程的方式,new出了兩個任務類對象,有各自的成員變量,相互之間不干擾。
然後再看一段來自JDK的解釋:
Runnable
接口應該由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱爲run
的無參數方法。
設計該接口的目的是爲希望在活動時執行代碼的對象提供一個公共協議。例如,Thread
類實現了Runnable
。激活的意思是說某個線程已啓動並且尚未停止。
此外,Runnable
爲非 Thread
子類的類提供了一種激活方式。通過實例化某個Thread
實例並將自身作爲運行目標,就可以運行實現Runnable
的類。大多數情況下,如果只想重寫run()
方法,而不重寫其他 Thread
方法,那麼應使用Runnable
接口。這很重要,因爲除非程序員打算修改或增強類的基本行爲,否則不應爲該類創建子類。(推薦使用創建任務類,並實現Runnable接口,而不是繼承Thread類)
採用繼承Thread類方式:
(1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
(2)缺點:因爲線程類已經繼承了Thread類,所以不能再繼承其他的父類。
採用實現Runnable接口方式:
(1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
(2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。