線程是比進程更輕量級的調度執行單位,各個線程既可以共享進程資源(內存地址、文件IO),又可以獨立調度(線程是CPU調度的最小單元)。
多進程和多線程的區別:
1、本質區別在於每個進程擁有自己一整套變量,線程則是共享數據。因此共享變量使線程之間的通信比進程之間的通信更有效、更容易。
2、與進程相比較,線程更“輕量級”,創建、撤銷一個線程比啓動新進程的開銷要小的多。
一、定義任務
1)通過實現Runnable接口並編寫run()方法,使得任務可以執行我們的命令。
Runnable執行工作的獨立任務,但不會返回任何值。
class LiftOff implements Runnable{
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
@Override
public void run() {
while (countDown-- > 0){
System.out.println(countDown);
Thread.yield();
}
}
}
2)希望任務結束後有一個返回值,需要實現Callable接口並編寫call方法。Callable是一個具有類型參數的泛型,它的類型參數表示是從方法call中返回的值,並且必須使用ExecutorService.submit()方法調用它。
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; i++){
results.add(exec.submit(new TaskWithResult(i)));
}
try {
for (Future<String> future: results){
future.get();
}
}catch (ExecutionException e){
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
submit()方法會產生Future對象,它用Callable返回結果的特定類型進行參數化,isDone()方法用來查詢Future是否已經完成,任務完成時,它具有一個結果,可以調用get()獲取該結果。也可以不用isDone()檢查直接調用get(),get()方法將阻塞,直至結果準備就緒。
二、線程的創建
1)Thread類來創建線程。Thread構造器只需一個Runnable對象。調用Thread對象的start()方法爲該線程執行必需的初始化操作,然後調用Runnable的run()方法,以便在這個新線程中啓動該任務
Thread t = new Thread(new LiftOff());
t.start();
start()調用後會快速的返回,run()方法會在新線程中執行
2)使用Executor創建線程。執行器(Executor)管理Thread對象,從而簡化併發編程。Executor在客戶端和任務執行之間提供了一個間接層;與客戶端直接執行任務不同,中介對象將執行任務。Executor允許你管理異步任務的執行,而無需顯式地管理線程的生命週期。
一般單個的Executor被用來創建和管理系統中所有的任務。shutDown()方法的調用可以防止新任務被提交給這個Executor。
FixedThreadPool可以一次性預先執行高昂的線程分配,限定線程的數量。可以節省時間,因爲不用爲每個線程都固定地付出創建線程的開銷。
CachedThreadPool通常會創建與所需數量相同的線程,然後在它回收舊線程時停止創建新線程。因此是首選,當這種方式會引發問題時,才需要切換到FixedThreadPool.
SingleThreadExecutor創建一個線程,每個任務都會在下一個任務開始之前運行結束,所有任務都使用相同的線程
三、線程常見名詞
3.1休眠 sleep()
調用sleep()將使任務終止執行給定的時間
對sleep()的調用可以拋出InterruptedException異常,異常不能誇線程傳播,所以必須在本地處理所有任務內部產生的異常
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
3.2線程優先級
線程的優先級將線程的重要性傳遞給調度器,儘管CPU處理線程集的順序不確定,但是調度器將傾向於讓優先級高的線程先執行。並不是優先級底的線程得不到執行,僅僅是執行的評率較低。
獲取線程優先級Thread.currentThread().getPriority();
設置線程優先級Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
設置優先級實在run()方法的開頭設定的,在構造器中設定不會有任何好處,因爲Executor在此刻還沒有執行任務
JDK有10個優先級,因爲與操作系統不能很好的映射,只使用MAX_PRIORITY, MIN_PRIORITY,NORM_PRIORITY三種級別。
3.3讓步 yield()
通過調用yield()可以給線程調度器暗示,讓別的線程使用CPU。yield()的調用也是建議具有相同優先級的線程可以運行。對於任何重要的控制或在調整應用時,都不能依賴於yield()。
3.4後臺線程 daemon
後臺線程指在程序運行的時候在後臺提供一種通用服務的線程,並且這種線程不屬於程序中不可或缺的部分。所有非後臺線程結束時,程序也就終止了,同時會殺死所有後臺線程。
必須在線程啓動之前調用setDaemon()方法,才能把它設置爲後臺線程。
Thread t = new Thread(new LiftOff());
t.setDaemon(true);
t.start();
通過定製ThreadFaactory可以定製由Executor創建的線程的屬性(後臺、優先級、名稱)
class DaemonThreadFactory implements ThreadFactory{
@Override
public Thread newThread(@NonNull Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
可以通過isDaemon()來判斷一個線程是否爲後臺線程。如果一個線程爲後臺線程那麼它創建的任何線程都是後臺線程。
注意:當最後一個非後臺線程終止時,後臺線程會“突然”終止,finally子句也不會執行
3.5加入一個線程join()
一個線程可以在其他線程之上調用join()方法,效果爲等待一段時間直到第二個線程結束才繼續執行。如某個線程在另一個線程t上調用t.join(),此線程將被掛起,直到目標線程t結束才恢復。
也可以在join()方法中帶一個參數,如果目標線程在參數時間到期還沒有結束,join()方法總能返回。
3.6捕獲異常
由於線程的本質特性,不能捕獲從線程中逃逸的異常。一旦異常逃逸出任務的run()方法,就會向外傳播到控制檯,除非採取特殊的步驟捕獲異常。
Thread.UncaughtExceptionHandler允許在每個Thread對象上附着一個異常處理器
Thread t = new Thread(new LiftOff());
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
}
});
t.start();