一、併發知識庫
二、Java線程實現及創建方式
2.1繼承Thread類
Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法是通過Thread類的start()的實例方法。start()方法是一個native方法,他將啓動一個新線程,並執行run()方法。
public class MyThreadExtendsThread extends Thread {
public void run() {
System.out.println("there is extends Thread!");
}
}
@Test
public void TestExtendsThread(){
MyThreadExtendsThread myThreadExtendsThread = new MyThreadExtendsThread();
myThreadExtendsThread.start();
}
2.2實現Runable接口
如果一個類已經繼承類其他類,則無法繼承Thread類。此時可以實現Runnable接口
public class ImplementsRunable implements Runnable {
@Override
public void run() {
System.out.println("there is implements Runnable!");
}
}
@Test
public void TestImplementsRunnable(){
//啓動一個MyThread需要先實例話一個Thread,傳人自己的MyThread實例
ImplementsRunable implementsRunable = new ImplementsRunable();
Thread t = new Thread(implementsRunable);
t.start();
}
//事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run()
public void run() {
if (target != null) {
target.run();
}
}
2.3ExecutorService、Callable、Future有返回值線程
有返回值的任務必須實現Callable接口,類似的,無返回值的任務實現Runnable接口。執行Callable任務後,可以獲得一個Future對象,執行Future對象的get方法就可以會的Callable任務返回的Object了。再接口線程池接口ExecutorService就可以實現有返回結果的多線程任務了。
public void call() {
//創建一個線程池
ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
//創建多個有返回值的任務
List<Future> futures = new ArrayList<Future>();
for (int i = 0; i < this.taskSize; i++) {
Callable callable = new MyCallable(i);
//執行任務並獲取Future對象
Future future = executorService.submit(callable);
futures.add(future);
}
// 關閉線程池
executorService.shutdown();
futures.forEach(future -> {
try {
// 從Future對象上獲取任務的返回值,並輸出到控制檯
System.out.println("future:" + future.get().toString());
} catch (InterruptedException | ExecutionException e) {
System.out.println("future error,e.message " + e.getLocalizedMessage());
}
});
}
public class MyCallable implements Callable {
int i;
@Override
public Object call() throws Exception {
return i;
}
public MyCallable(int i) {
this.i = i;
}
public MyCallable() {
}
}
2.4基於線程池的方式
基於線程池的方式線程和數據庫連接這些資源都是非常寶貴的資源。那麼每次需要的時候創建,不需要的時候銷燬,是非常浪費資源的。那麼我們就可以使用緩存的策略,也就是使用線程池。
// 創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
while(true) {
executorService.execute(newRunnable() {
// 提交多個線程任務,並執行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try{
Thread.sleep(3000);
} catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "sleep error");
}
}
});
}
三、四種線程池
Java裏線程池的頂級接口是Executors,但嚴格意義上講Executors不是線程池,而是一個執行線程但工具,真正的線程池接口是ExecutorService。
3.1newCachedThreadPool
創建一個可根據需要創建線程的線程池,但是再以前創建的線程可用時會重用他們。對於很多執行短期異步任務的程序而言,這些線程池通常可以提高他們的性能。調用executor將重用以前構造的線程(如果他們可用)。如果線程沒有可用的,則創建一個新線程並添加到池中。終止並移除緩存中那些已有60s未被使用的線程。 因此長時間保持空閒的線程池不會佔用大量的系統資源。
3.2newFixedThreadPool
創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。再任意點,在大多數n Threads線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,任務會在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。
3.3newScheduledThreadPool
創建一個線程池,它可在給定延時後執行命令或定期執行。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(newRunnable(){
@Override
public void run() {
System.out.println("延遲三秒");
}
},3,TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
@Override
public void run() {
System.out.println("延遲1秒後每三秒執行一次");
}
},1,3,TimeUnit.SECONDS);
3.4newSingleThreadExecutor
Executors.newSingleThreadExecutor會創建一個線程池(這個線程池只有一個線程),這個線程池會在線程死亡後(或發生異常時),重新啓動一個新的線程來代替原來的線程繼續執行下去。
四、線程的聲明週期(狀態)
在線程被啓動後,它不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,他會經歷創建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是在線程啓動後,它不可能一直霸佔着cpu獨自運行,所以需要CPU在多條線程之間來回切換,於是線程也會在阻塞、運行之間來回切換。
4.1新建狀態(New)
當程序使用new關鍵字創建一個線程後,該線程就處於新建狀態,此時僅由JVM爲其分配內存,並初始化其成員變量的值。
4.2就緒狀態(Runnable)
當線程方法調用start()方法後,該線程就處於就緒狀態。Java虛擬機會爲其創建方法調用棧和程序計數器,等待調度運行。
4.3運行狀態(Running)
如果處於就緒狀態獲取了CPU,開始執行run()方法的線程體,則該線程處於運行狀態。
4.4阻塞狀態(Blocked)
阻塞狀態是指線程因爲某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,纔有機會再次獲得cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種:
- 等待阻塞(o.wait->等待對列)
運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。 - 同步阻塞(lock->鎖池)
運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。 - 其他阻塞(sleep/join)
運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。
4.5線程死亡
線程會以下面三種方式結束,結束後就是死亡狀態。
4. 正常結束:run()或call()方法執行完成,線程正常結束。
5. 異常結束:線程拋出一個未捕獲的Exception或Error。
6. 調用stop:直接調用該線程的stop()方法來結束該線程—該方法通常容易導致死鎖,不推薦使用。
五、sleep和wait的區別
- 對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
- sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
- 在調用sleep()方法的過程中,線程不會釋放對象鎖。
- 而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。
六、start和run的區別
- start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下面的代碼。
- 通過調用Thread類的start()方法來啓動一個線程,這時此線程是處於就緒狀態,並沒有運行。
- 方法run()稱爲線程體,它包含了要執行的這個線程的內容,線程就進入了運行狀態,開始運行run函數當中的代碼。Run方法運行結束,此線程終止。然後CPU再調度其它線程。