執行緒的啟動和執行
方法一:使用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高併發核心程式設計》