創建執行緒的4種方法 and 執行緒的生命周期
執行緒的啟動和運行
方法一:使用start()方法:用來啟動一個執行緒,當調用start方法後,JVM會開啟一個新執行緒執行用戶定義的執行緒程式碼邏輯。
方法二:使用run()方法:作為執行緒程式碼邏輯的入口方法。run方法不是由用戶程式來調用的,當調用start方法啟動一個執行緒之後,只要執行緒獲得了CPU執行時間,便進入run方法去執行具體的用戶執行緒程式碼。
start方法用於啟動執行緒,run方法是用戶邏輯程式碼執行入口。
1.創建一個空執行緒
main {
Thread thread = new Thread();
thread.start();
}
程式調用start方法啟動新執行緒的執行。新執行緒的執行會調用Thread的run方法,該方法是業務程式碼的入口。查看一下Thread類的源碼,run方法的具體程式碼如下:
public void run() {
if(this.target != null) {
this.target.run();
}
}
這裡的target屬性是Thread類的一個實例屬性,該屬性非常重要,後面會講到。在Thread類中,target屬性默認為空。在這個例子中,thread屬性默認為null。所以在thread執行緒執行時,其run方法其實什麼也沒做,執行緒就執行完了。
2.繼承Thread類創建執行緒
new Thread(() -> {
System.out.println(1);
}).start();
3.實現Runnable介面創建執行緒
class TestMain implements Runnable {
main {
new Thread(TestMain::new).start();
}
@Override
public void run() {
System.out.println(12);
}
}
4.使用Callable和FutureTask創建執行緒
前面的Thread和Runnable都不能獲取非同步執行的結果。為了解決這個問題,Java在1.5之後提供了一種新的多執行緒創建方法:Callable介面和FutureTask類相結合創建執行緒。
1.Callable介面
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable介面是一個泛型介面,也是函數式介面。其唯一的抽象方法call有返回值。
Callable能否和Runnable一樣,作為Thread實例的target使用呢?
答案是不可以。因為Thread的target屬性類型為Runnable,所以一個在Callable與Thread之間搭橋接線的重要介面即將登場。
2.RunnableFuture介面
這個重要的介面就是RunnableFuture介面,他實現了兩個目標,一是可以作為Thread實例的target實例,二是可以獲取非同步執行的結果。
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
通過源碼可以看出:RunnableFuture是通過繼承Runnable和Future來實現上面2個目標的。
3.Future介面
Future介面至少提供了三大功能:(1)取消執行中的任務(2)判斷任務是否完成(3)獲取任務的執行結果
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
- V get():獲取非同步任務執行的結果,這個方法的調用是阻塞性的。如果非同步任務沒有執行完成,會一直被阻塞直到任務執行完成。
總體來說,Future是一個對非同步任務進行交互、操作的介面。但是Future只是一個介面,它沒有辦法直接完成對非同步任務的操作,JDK提供了一個默認的實現類-----FutureTask。
4.FutureTask類
FutureTask類是Future介面的實現類,提供了對非同步任務的操作的具體實現。但是FutureTask類不僅實現了Future介面,還實現了Runnable介面,更精準的說FutureTask類實現了RunnableFuture介面。
前面提到RunnableFuture介面很關鍵,既可以作為Thread執行緒實例的target目標,又可以獲取並發任務執行的結果,是Thread與Callable之間一個非常重要的搭橋角色。
關係圖如下:

可以看出,FutureTask既能作為一個Runnable類型的target,又能作為Future非同步任務來獲取Callable的計算結果。
FutureTask是如何完成多執行緒的並發執行、任務結果的非同步獲取呢?他的內部有一個Callable類型的成員:
private Callable<V> callable;
Callable實例屬性用來保存並發執行的Callable類型的任務,並且Callable實例屬性需要在FutureTask實例構造時初始化。FutureTask實現了Runnable介面,在run方法的實現中會執行Callable的call方法。
FutureTask內部有一個outcome實例屬性用於保存執行結果。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
使用Callable和FutureTask創建執行緒的具體步驟
(1)創建Callable介面的實現類,並實現call方法,編寫好具體邏輯。
(2)使用Callable實現類的實例構造一個FutureTask實例。
(3)使用FutureTask實例作為Thread構造器的target入參,構造新的Thread執行緒實例
(4)調用Thread實例的start方法啟動新執行緒,內部執行過程:啟動Thread實例的run方法並發執行後,會執行FutureTask實例的run方法,最終會並發執行Callable實現類的call方法。
(5)調用FutureTask對象的get方法阻塞的獲得結果。
public static void main(String[] args) {
new Thread(new FutureTask<>(() -> {
int i = 1;
return i;
})).start();
}
5.執行緒池創建執行緒
1.執行緒池的創建與執行目標提交
創建一個固定3個執行緒的執行緒池。
private static ExecutorService pool = Executors.newFixedThreadPool(3);
向ExecutorService執行緒池提交非同步執行target目標任務的常用方法有:
// 方法一:無返回
void execute(Runnable command);
// 返回一個Future實例
<T> Future<T> submit(Callable<T> task);
// 也是返回Future實例
Future<?> submit(Runnable task);
2.執行緒池使用實戰
public class TestMain {
public static ExecutorService pool = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws ExecutionException, InterruptedException {
pool.execute(() -> {
int i = 1;
});
final Future<?> submit = pool.submit(() -> {
int i = 1;
return i;
});
Integer o = (Integer) submit.get();
System.out.println("o: " + o);
AtomicInteger ans = new AtomicInteger();
final Future<AtomicInteger> submit1 = pool.submit(new FutureTask<>(() -> {
int i = 1;
ans.set(i);
return ans;
}), ans);
System.out.println("ans: " + submit1.get());
pool.shutdown();
}
Lambda中不允許使用局部變數:因為使用的是局部變數的副本,對局部變數本身不起作用,所以不能使用。但是可以使用引用類型的變數,比如原子類的ans變數,這才會對原值產生影響。

execute與submit區別:
(1)接收參數不一樣。
(2)submit有返回值,execute沒有返回值。
說明:實際生產環境禁止使用Executors創建執行緒池。
執行緒的核心原理
1.執行緒的生命周期
public static enum State {
NEW, 新建
RUNNABLE, 可執行
BLOCKED, 阻塞
WATTING, 等待
TIMED_WAITING, 超時等待
TERMINATED; 終止
}
在定義的6種狀態中,有4種比較常見的狀態,他們是:NEW、RUNNABLE、TERMINATED、TIMED_WAITING。
- NEW狀態:創建成功但是沒有調用start方法啟動的執行緒實例都處於NEW狀態。
當然,並不是執行緒實例的start方法一調用,其狀態就從NEW到RUNNABLE,此時並不意味著執行緒立即獲取CPU時間片並且立即執行。、
- RUNNABLE狀態:前面說到,當調用了執行緒實例的start方法後,下一步如果執行緒獲取CPU時間片開始執行,JVM將非同步調用執行緒的run方法執行其業務程式碼。那麼在run方法被調用之前,JVM在做什麼呢?
JVM的幕後工作和作業系統的執行緒調度有關。當Java執行緒示例的start方法被調用後,作業系統中對應執行緒並不是運行狀態,而是就緒狀態,而Java執行緒沒有就緒態。就緒態的意思就是該執行緒已經滿足執行條件,處於等待系統調度的狀態,一旦被選中就會獲得CPU時間片,這時就變成運行態了。
在作業系統中,處於運行狀態的執行緒在CPU時間片用完後,又回到就緒態,等待CPU的下一次調度。就這樣,作業系統執行緒在就緒態和運行態之間被反覆調度,知道執行緒的程式碼邏輯完成或者異常終止為止。這時執行緒進入TERMINATED狀態。
就緒態和運行態都是作業系統的執行緒狀態。在Java中,沒有細分這兩種狀態,而是將他們二合一,都叫作RUNNABLE狀態。這時Java執行緒狀態和作業系統不一樣的的地方。
總結:NEW狀態的執行緒實例調用了start方法後,執行緒的狀態變為RUNNABLE。但是執行緒的run方法不一定會馬上執行,需要執行緒獲取了CPU時間片之後才會執行。
- TERMINATED狀態:run方法執行完成之後就變成終止狀態了,異常也會。
- TIME_WAITING狀態:處於等待狀態,例如Thread.sleep,Objects.wait,Thread.join等。(主動)
- BLOCKED狀態:處於此狀態的執行緒不會佔用CPU資源,例如執行緒等待獲取鎖,IO阻塞。(被動)
2.執行緒狀態實例
讓五個執行緒處於TIME-WAITING狀態,使用Jstack查看。
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 500; j++) {
try {
Thread.sleep(500);
System.out.println(j);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624761800 nid=0x2fcc waiting on condition [0x0000003944bff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run([email protected]/Thread.java:834)
"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624762000 nid=0x1d74 waiting on condition [0x0000003944cfe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run([email protected]/Thread.java:834)
"Thread-2" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624763800 nid=0x3f08 waiting on condition [0x0000003944dfe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run([email protected]/Thread.java:834)
"Thread-3" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624765000 nid=0x2b5c waiting on condition [0x0000003944eff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run([email protected]/Thread.java:834)
"Thread-4" #18 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624768000 nid=0x52f0 waiting on condition [0x0000003944ffe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep([email protected]/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run([email protected]/Thread.java:834)
Re
《Java高並發核心編程》


