多執行緒之前學過,很久沒用忘得差不多了,重新學習一下,然後再看《Java併發程式設計的藝術》。
一、程式與執行緒
計算機同時執行多個程式,每個執行中的程式就是一個程式。每一個程式內部又有多個執行緒。
程式有如下三個特徵:
(1)獨立性:它可以有自己獨立的資源,有自己的私有地址空間;
(2)動態性:程式是一個在系統中活動的指令集合,具有自己的生命週期和不同的狀態;
(3)併發性:多個程式可以在單個處理器上併發執行。
併發性與並行性:
(1)併發指在同一時刻只有一條指令執行,處理器快速切換多個程式指令;
(2)並行性指在同一時刻,有多條指令在多個處理器上同時執行。
現代作業系統都支援多程式的併發,常用方式有:共用式多工操作策略和搶佔式多工操作策略。
二、建立執行緒的方式
1、繼承Thread類
1)繼承Thread類,並重新寫run()方法;
2)建立上述類的例項,即建立執行緒物件;
3)呼叫start()方法啟動執行緒。
public class FirstThread extends Thread {
private int i=0;
public void run() {
for(;i<100;++i) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;++i) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new FirstThread().start();
new FirstThread().start();
}
}
}
}
複製程式碼
Thread.currentThread()是Thread類的靜態方法,返回當前正在執行的執行緒物件; getName()是Thread類的例項方法,返回撥用該方法的執行緒的名字。
2、實現Runnable介面
1)定義Runnable介面的實現類,重寫run()方法
2)建立Runnable實現類的例項,並作為Thread類的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
可以在建立Thread類物件的時候為該物件取一個名字:new Thread(secondThread,"執行緒2");
public class SecondThread implements Runnable {
private int i = 0;
@Override
public void run() {
for (; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
SecondThread st1 = new SecondThread();
new Thread(st1,"new-1").start();
new Thread(st1,"new-2").start();
}
}
}
}
複製程式碼
Runnable介面是函式式介面,可以使用Lambda表示式建立Runnable物件。
上面程式碼中,多個執行緒共享了一個target。
3、使用Callable和Future建立執行緒
Callable像是Runnable的增強版:
call()方法可以有返回值;
call()方法可以宣告,丟擲異常。
Java5提供了Future介面來代表Callable介面中call()方法的返回值。它有FutureTask實現類,該類實現了Future介面和Runnable介面,可以作為Thread類的Target。
該方法建立執行緒的步驟:
1)建立Callable介面的實現類,並實現call()方法,call方法為執行緒執行體,可以有返回值。再建立Callable的例項。
2)使用FutureTask類來包裝Callable物件。
3)使用FutureTask物件作為Thread物件的target建立並啟動執行緒。
4)可以使用FutureTask類的get()方法來獲得子執行緒執行結束後的返回值。
public class ThirdThread {
public static void main(String[] args) {
ThirdThread rt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()-> {
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值:"+i);
}
return i;
});
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值:"+i);
if(i==20) {
new Thread(task,"有返回值的執行緒").start();
}
}
try {
System.out.println("子執行緒的返回值:"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
三、執行緒的生命週期
線上程的生命週期中,要經過:新建、就緒、執行、阻塞、死亡5種狀態。
當使用new關鍵字建立了一個執行緒後,該執行緒就處於新建狀態。(此時還沒有表現出來執行緒的動態特性);當呼叫start()方法後,執行緒處於就緒狀態,JVM會為其建立方法呼叫棧和程式計數器;當就緒狀態的執行緒獲得了CPU資源,開始執行run()方法,此時處於執行狀態;當發生如下情況時,程式處於阻塞狀態:
1執行緒呼叫sleep()方法主動放棄處理器資源;
2該執行緒檢視獲得一個同步監視器,但是該監視器被其他執行緒持有;
3執行緒在等待通知notify
4呼叫了執行緒的suspend()方法將執行緒掛起。
而當sleep了指定時間;獲得了同步監視器;其他執行緒發出了通知;掛起的執行緒被呼叫了resume()的方法,時可以解除阻塞,讓執行緒重寫處於就緒狀態。
當:1run或call方法執行完成;2執行緒丟擲異常或error;3直接呼叫stop()方法結束該執行緒(容易死鎖),該執行緒死亡。