Java 多執行緒
程序和執行緒的概念
程序
幾乎所有的作業系統都支援程序的概念。一個任務通常對應一個程序。程序具有如下特徵:
- 程序通常是獨立存在的,擁有自己獨立的資源。
- 程序擁有自己的生命週期和各種不同的狀態。
- 多個程序可以在單個處理器上併發執行,多個程序之間不會互相響應。
執行緒
執行緒是cpu執行的最小單元,一個程序可以有多個執行緒。一個執行緒必須有一個父程序。執行緒可以擁有自己的堆疊,但不擁有系統資源。對 cpu 而言,在同一時間,只能執行一個執行緒。之所以看起來像是在同時執行,是因為cpu輪循的時間特別快。執行緒由程序來負責管理和排程。
執行緒的生命週期
在網上扒了一張圖,足夠清晰明瞭。
Java執行緒具有五種基本狀態
新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread();
就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行;
執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就 緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;
阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:
1.等待阻塞:執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;
2.同步阻塞 -- 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態;
3.其他阻塞 -- 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。
需要注意的是: wait() 、notify()、notifyAll() 方法是 Object 類的方法。
java 中執行緒的建立和啟動
1.繼承自 Thread 類建立執行緒:
- 定義類繼承自 Thread 類,重寫該類的 run() 方法,run() 方法方法體代執行緒要執行的任務。因此,run() 方法也稱執行緒執行體。
- 建立定義的執行緒類的例項,即建立執行緒物件。
- 呼叫執行緒物件的 start() 方法啟動該執行緒。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName() + "-----" + i);
}
}
public static void main(String[] args){
new MyThread().start();
new MyThread().start();
}
}
擷取部分執行結果如下:
2.實現Runnable介面建立執行緒:
- 定義 Runnable 介面的實現類,並重寫該介面的 run()方法,該 run() 同樣是執行緒執行體。
- 建立 Runnable 實現類的例項,將 Runnable 實現類的例項傳入作為引數傳入,建立 Thread 類的例項,作為執行緒物件。
- 呼叫執行緒物件的 start() 方法啟動執行緒。
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "-----" + i);
}
}
public static void main(String[] args){
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
}
}
擷取部分執行結果如下:
3.使用 Callable 和 Future 建立執行緒:
- 建立 Callable 的實現類,並實現 call() 方法,call() 方法為執行緒執行體。泛型引數為 call() 返回值。
- 建立 Callable 實現類的例項,並將該物件傳入,建立 FutureTask 類的例項。該 FutureTask 的物件封裝了 該 Callable 例項物件的返回值。
- 建立 FutureTask 的例項,作為 target 傳入,建立 Thread 類的物件,作為執行緒物件。
- 呼叫執行緒物件的 start() 方法啟動執行緒。
- 如有必要,可以通過 FutureTask 物件的 get() 方法來獲得執行緒執行結束後的返回值。
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "-----" + i);
}
return i;
}
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask<Integer> task = new FutureTask<Integer>(mc);
new Thread(task).start();
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println("----------" + task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
值得一提的是:雖然直接繼承自 Thread 類的方式實現起來最為簡單,但是由於java的類只能單繼承,但可以實現多個介面。所以一般我們不採用該方式。另外,啟動執行緒的方式,都是通過呼叫執行緒物件的 start() 方法,切莫不要直接呼叫 run() 方法,這樣實際是把該執行緒類當作了一個普通類而已。