前言
?多任務處理在現實開發場景中已經無處不在,通過多任務處理可以將計算機性能更大程度的發揮出來,避免處于空閑狀態浪費性能。
?對于計算量相同的任務,程序線程并發協調得越有條不紊,效率自然就會越高;反之,線程之間頻繁爭用數據,互相阻塞甚至死鎖,將會大大降低程序的并發能力。
?因此我們有必要深入了解多線程開發。
1 概念
在說線程之前我們先來了解進程、線程、線程池概念。
-
進程(process)
是指在系統中正在運行的一個應用程序。 -
線程(Thread)
是比進程更輕量級的調度執行單位,它可以把一個進程的資源分配和執行調度分開。目前線程是CPU調度執行的最基本單位。 -
線程池(ThreadPool)
自動創建、銷毀線程的一個容器。
2 線程
?不建議直接new線程,因為存在以下幾點問題:
①不易復用,頻繁創建及銷毀開銷大
②特定場景不易使用,例如定時任務
?一個簡單的線程實現方式
fun doSomething() {
Thread{
//todo
}.start()
}
3 線程池
?線程池的優點:
①可重用線程池中的線程,避免頻繁創建及銷毀帶來的性能開銷。
②可控制最大并發數,避免大量線程之間因為相互搶占系統資源而導致阻塞。
③支持特定場景使用,例如定時任務。
?我們日常開發中所使用到的線程池在java.util.concurrent
并發工具包下,繼承自Executor實現了ThreadPoolExecutor,ThreadPoolExecutor提供了一系列參數配置線程,如下所示:
/**
*
* @param corePoolSize 核心線程數,默認情況一直存活
*
* @param maximumPoolSize 線程池最大線程數
*
* @param keepAliveTime 非核心線程閑置時的超時時長,超過這個時長就會被
* 回收。當allowCoreThreadTimeOut設置為true時作用于核心線程 。
*
* @param unit 指定keepAliveTime存活時常的單位,TimeUnit是個枚舉類,一般
* 有TimeUnit.SECONDS,TimeUnit.MINUTES
*
* @param workQueue 線程池任務隊列,通過execut提交Runnable執行。
*
* @param threadFactory 當創建新線程時可以使用該工廠創建
*
* @throws 滿足以下條件拋出此異常 IllegalArgumentException
* {corePoolSize < 0}
* {keepAliveTime < 0}
* {maximumPoolSize <= 0}
* {maximumPoolSize < corePoolSize}
* @throws NullPointerException workQueue、threadFactory為null時拋出異常。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
?除以上參數外,還有一個參數RejectedExecutionHandler,當任務隊列已滿或者是無法成功執行任務,就會調用RejectedExecutionHandler的rejectedExecution,默認拋出異常。ThreadPoolExecutor還為我們提供了CallerRunsPolicy
、AbortPolicy
、DiscardPolicy
、DiscardOldestPolicy
四種Handler,默認為AbortPolicy,因此是直接拋出異常。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
?一個簡單的線程池工具類創建如下:
private int CPU_Runtime = Runtime.getRuntime().availableProcessors() + 1;
public ThreadPoolExecutor threadPoolExecutor;
public ThreadPoolUtils() {
threadPoolExecutor = new ThreadPoolExecutor(CPU_Runtime, CPU_Runtime,
1, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(128),
new MyThreadFactory("test"));
}
/**
* 記錄是線程池中第幾個線程
*/
private static class MyThreadFactory implements ThreadFactory {
private final String name;
private final AtomicInteger mCount = new AtomicInteger(1);
MyThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(@NonNull Runnable r) {
return new Thread(r, name + "-" + mCount.getAndIncrement() + "-");
}
}
?根據實際開發需求做了以上這些配置,配置參數規格如下:
- 核心線程數為CPU核心數+1
- 非核心線程數與核心線程數相同
- 非核心線程超時時間為1分鐘
- 任務隊列容量為128
- 使用工廠模式創建線程,便于查看當前執行的線程相關信息。
4 常用的4種線程池
?java還為我們實現了配置了4種常用的線程池,newFixedThreadPool
、newCachedThreadPool
、newScheduledThreadPool
、newSingleThreadExecutor
,它們都直接或間接配置了ThreadPoolExecutor。
4.1 newFixedThreadPool
?固定線程數的線程池,當處于空閑狀態不會被回收。因為最大線程數和核心線程相等,所以都是核心線程,處于空閑狀態不會被回收。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
4.2 newCachedThreadPool
?數量不固定的線程池,因為最大線程數為Integer.MAX_VALUE,但是一般不會創建太多,因為創建線程開銷比較消耗性能。默認超時時長為60S。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.3 newScheduledThreadPool
?創建固定數量的核心線程以及Integer.MAX_VALUE非核心線程,默認超時時長為10分鐘,一般用于執行固定周期的重讀任務。
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
4.4 newSingleThreadExecutor
?只有一個核心線程的線程池,它保證了所有的線程在此隊列中都是有序執行的。一般用于統一外界任務,避免處理同步問題。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
總結
?java.util.concurrent為我們提供了線程池的創建,方便我們管理線程,但隨著變成語言的發展,協程也出現在了我們日常開發中,無論是Java即將面世的遷程(Fiber)或者kotlin的協程(Coroutines),它們的創建與銷毀的開銷遠遠小于線程的創建,因此在使用kotlin開發Android時建議多使用協程進行多并發開發。