文章目錄
單線程的使用方式
java.lang.Runnable
代碼實現方式:
- 構造線程任務類
MyRunnable
,實現Runnable
接口 - 以
MyRunnable
對象爲參數,構造線程Thread
實例
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("任務實現");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
這種線程實現方式側重於“線程”和“任務”的獨立性,也就是概念的解藕,“線程”不關心“任務”的具體實現,“任務”也不關心“線程”的管理方式。
java.lang.Thread
代碼實現方式:
- 繼承線程管理類
Thread
,重寫run()
實現具體的線程任務 - 構造線程類
MyThread
實例
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("任務實現");
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
這種線程實現方式側重於“任務線程”的整體概念,實現類MyThread
既具備線程管理功能,又含有具體的任務實現。“線程”和“任務”同生共死,相依爲命。
因爲Java
不支持多繼承,所以,如果實現類MyThread
還有另外的extends
需求,就不能採用這種線程實現方式了。
java.util.concurrent.FutureTask
代碼實現方式:
- 實現
java.util.concurrent.Callable<V>
接口,重寫call()
實現具體任務 - 以
MyCallable
對象實例爲參數,構造出FutureTask
對象實例 - 再以
FutureTask
對象實例爲參數,構造出Thread
對象實例 - 任務線程執行的時候,可以通過
FutureTask
對象的get()
阻塞當前線程,直到任務線程執行完畢,返回任務執行結果。
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("任務實現");
return "線程執行結果";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
String result = task.get();
System.out.println(result);
}
}
這種線程實現方式略顯複雜,編碼的時候會付出一定的代價,但是換來的結果是有價值的。不僅解藕了“線程”和“任務”的概念,並且“當前線程”還能獲取到“任務線程”異步執行的結果。
也就是說,任務執行過程可控,這個特性在某些業務場景下非常具有實用價值。而這,也是前面兩種線程實現方式很難達到的效果。
線程池的使用方式
創建線程池
Java
線程池的整體框架主要是基於以下幾個接口實現的:
java.util.concurrent.Executor
java.util.concurrent.ExecutorService
java.util.concurrent.ScheduledExecutorService
java.util.concurrent.ThreadFactory
java.util.concurrent.Callable
大多數情況下,開發者會藉助java.util.concurrent.Executors
創建並管理線程池。
Executors
就是圍繞線程池框架產生的一個工具類,可類比集合工具類java.util.Collections
,還有數組工具類java.util.Arrays
。都是JDK
提供給開發者的福利,具有很高實用價值的工具類,通常情況下,開發者無需重複造輪子。
Executors
可創建以下幾類線程池:
package java.util.concurrent;
public class Executors {
// 單線程工作的線程池,任務按提交順序依次執行
public static ExecutorService newSingleThreadExecutor() { ... }
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { ... }
// 最大線程數量固定的線程池,線程數量達到最大後保持不變
public static ExecutorService newFixedThreadPool(int nThreads) { ... }
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { ... }
// 線程空閒後可以緩存一段時間的線程池,最大線程數量沒有限制,多線程併發場景下使用很危險!
public static ExecutorService newCachedThreadPool() { ... }
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { ... }
// 提交的任務可以被週期性執行的線程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { ... }
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { ... }
// 單線程的線程池,週期性執行提交的任務
public static ScheduledExecutorService newSingleThreadScheduledExecutor() { ... }
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { ... }
// 線程搶佔式執行提交任務的線程池,底層原理和上面的幾種線程池不太一樣
public static ExecutorService newWorkStealingPool(int parallelism) { ... }
public static ExecutorService newWorkStealingPool() { ... }
}
提交線程任務
通過Executors
工具類創建線程池後,提交任務的方法有四個,一個定義在Executor
接口當中,三個定義在ExecutorService
接口當中:
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
}
單看這四個方法定義,就可以發現它們最大的區別在於方法返回值.通過execute
提交的任務沒有返回值,通過submit
提交的任務有返回值。
關閉線程池
使用完線程池後,還應該主動關閉線程池。關閉線程池的方法有兩個,都定義在ExecutorService
接口裏面:
public interface ExecutorService extends Executor {
// 線程池不再接收新任務,等待池中已有任務執行完畢後,銷燬線程池資源
void shutdown();
// 線程池不再接收新任務,儘可能停止正在執行的任務,尚未執行的任務列表作爲方法的返回值,銷燬線程池資源
List<Runnable> shutdownNow();
}
需要注意的是,主動調用關閉線程池的方法後,線程池也不是一定就能很快的關閉,因爲線程池可以被關閉的條件是池中沒有處於活動狀態的任務。
如果線程池正在執行的某些任務非常耗時,線程池也會等到這些耗時任務都執行完畢後再銷燬資源。
如果線程池中某些正處於活動狀態的任務不能響應線程停止的消息,無法被有效的停止,線程池就沒辦法關閉,也不能銷燬資源。