線程的啟動和運行
方法一:使用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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/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(java.base@11.0.11/Thread.java:834)
Re
《Java高並發核心編程》