1. 概述
java中實現多線程最直接、最基礎的做法是實現Runnable接口、繼承Thread類和實現Callable接口。對於每一個任務創建一個線程的方式,如果任務數量過多,過度消耗資源,會引起內存溢出的問題(Out of Memory)。
實際上,java線程池的引入也來源於生活中的實際例子。我們去火車站買票,如果每來一人購票,車站就開一窗口,那麼很快車站的資源就耗盡了,沒有場地,沒有資金,去一直開下去。假設車站在平時開設5個窗口,我們稱其爲核心(core)窗口,那麼國慶假期或春運期間,購票人數激增,車站會增加窗口,而受資源限制,車站可將窗口最多增至10個,如果超過10人以上來購票,就需要在窗口按照“先來後到(FIFO)”的方式進行排隊等候了。
java自JDK 1.5引入了線程池和Executor框架,至此,開發者就可以像“火車站購票”一樣,方便的使用線程池對多線程進行管理了。
2. Executor接口
來自JDK1.5的 java.util.concurrent 包。
void execute(Runnable command)
Executor是一個Functional interface,提供了一個接受Runnable命令的方法。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
可以像上面的例子一樣使用Executor,但這不是重點,配合線程池才能發揮其作用。
public class ThreadPoolDemo {
private static final int NTHREADS = 100;
private static final Executor es = Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) {
es.execute(() -> System.out.println("execute task"));
}
}
Executors.newFixedThreadPool(NTHREADS)
創建了一個有NTHREADS數量的線程池。當有新的任務提交時,如果線程池有空餘的線程,則立即執行,否則,新的任務會放入隊列等待。
3. Executor框架提供的線程池
3.1 ExecutorService接口
public interface ExecutorService extends Executor
Executors接口繼承了Executor接口,它提供了shutdown()
方法,可以關閉線程池,不再接受新的任務。同時,Executors還增加了submit
方法返回一個Future
對象,來獲取異步任務返回的結果。
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
3.2 JDK提供的線程池
Executors類提供了一些工廠方法創建線程池:
public static ExecutorService newFixedThreadPool(int nThreads)
創建一個線程池,只有固定數量的線程。在任何時候,線程池中最多有nThreads個激活的線程,如果有額外的任務提交,而沒有多餘的線程可用時,額外的任務就需要在隊列中進行等待。如果有線程因爲異常退出,則會補充新的線程。
public static ExecutorService newSingleThreadExecutor()
創建只有一個線程的線程池。
public static ExecutorService newCachedThreadPool()
根據需要創建線程。如果線程池中有可複用的線程,就優先使用可複用的線程,否則添加新的線程。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創建指定大小的線程池,任務可在指定的延遲時間後執行或週期性的執行。
3.3 自定義線程池
3.2節提到的jdk提供的創建線程池的方法,底層是通過實例化ThreadPoolExecutor
去實現的。newFixedThreadPool
創建固定數量的線程池,當任務過多時,會造成任務的堆積;newCachedThreadPool
根據需要創建,又會造成資源浪費,甚至Out of Memory的問題。一般在項目中更多的是根據需要自定義線程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
- 線程池中始終保留的線程個數;
maximumPoolSize
- 允許在線程池中存在的最大線程個數;
keepAliveTime
- 當線程池中線程的數量超過corePoolSize
時,多餘的空閒線程的存活時間;
unit
- keepAliveTime
的單位;
workQueue
- 保存已提交但未執行的任務的隊列;
threadFactory
- 線程工廠;
handler
- 拒絕策略,當任務過多,來不及執行時的拒絕策略。
備註:ExecutorService 繼承了 Executor 接口,ThreadPoolExecutor 實現了 ExecutorService。
4. 一個自定義線程的例子
public class MyThreadPool {
private static ThreadFactory myThreadPoolFactory =
new ThreadFactoryBuilder()
.setNameFormat("MyThread ThreadFactory-pool")
.build();
private static ThreadPoolExecutor EXECUTOR =
new ThreadPoolExecutor(
12,
100,
50L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
myThreadPoolFactory,
new ThreadPoolExecutor.AbortPolicy());
private static Future<Integer> fun(int x) {
return EXECUTOR.submit(() -> x * 2 - 5);
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 8, 9, 10};
List<Future<Integer>> futures = Lists.newArrayList();
Arrays.stream(arr).forEach(x -> futures.add(fun(x)));
try {
List<Integer> list = Lists.newArrayList();
for(Future<Integer> f : futures) {
list.add(f.get());
}
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 超載拒絕策略
線程池提供了4種拒絕策略:
static class ThreadPoolExecutor.AbortPolicy
直接拋出一個RejectedExecutionException
,阻止系統正常工作。
static class ThreadPoolExecutor.CallerRunsPolicy
只要線程池未關閉,該策略直接在調用者線程中運行被丟棄的任務。
static class ThreadPoolExecutor.DiscardOldestPolicy
該策略丟棄最老的任務,也就是丟棄即將被執行的任務,然後重新提交當前任務。這個策略更看重的是當前提交的任務。“只見新人笑,那關舊人哭。”
static class ThreadPoolExecutor.DiscardPolicy
這個策略則是直接把當前無法執行的任務丟棄了。
附錄:多線程中類和接口的繼承關係(來源於網絡)
todo
scheduleExecutorService的例子。