[JDK8] Java線程的幾種使用方法

單線程的使用方式

java.lang.Runnable

代碼實現方式:

  1. 構造線程任務類MyRunnable,實現Runnable接口
  2. 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

代碼實現方式:

  1. 繼承線程管理類Thread,重寫run()實現具體的線程任務
  2. 構造線程類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

代碼實現方式:

  1. 實現java.util.concurrent.Callable<V>接口,重寫call()實現具體任務
  2. MyCallable對象實例爲參數,構造出FutureTask對象實例
  3. 再以FutureTask對象實例爲參數,構造出Thread對象實例
  4. 任務線程執行的時候,可以通過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();
}

需要注意的是,主動調用關閉線程池的方法後,線程池也不是一定就能很快的關閉,因爲線程池可以被關閉的條件是池中沒有處於活動狀態的任務。

如果線程池正在執行的某些任務非常耗時,線程池也會等到這些耗時任務都執行完畢後再銷燬資源。

如果線程池中某些正處於活動狀態的任務不能響應線程停止的消息,無法被有效的停止,線程池就沒辦法關閉,也不能銷燬資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章