Java:多线程之线程池
參考博文:https://www.cnblogs.com/dolphin0520/p/3932921.html
前文講過,使用線程的時候就手動創(chuàng)建并啟動一個線程,使用完后線程被銷毀,這樣就會有一個問題:
如果并發(fā)的線程數(shù)量非常多,并且每個線程都是執(zhí)行一個時間很短的任務(wù)就結(jié)束了,這樣頻繁的創(chuàng)建線程就會大大降低系統(tǒng)的效率,因為頻繁的創(chuàng)建線程和銷毀線程都需要時間。
那么有沒有一種辦法使得線程可以服用呢?就是執(zhí)行完一個任務(wù),并不銷毀,而是可以繼續(xù)執(zhí)行其他任務(wù)。
Java為我們提供了線程池來達到這樣的效果。今天我們就來講解一下Java的線程池。首先我們從最核心的 ThreadPoolExecutor 類中的方法講起,然后給出它的使用示例,最后討論如何合理配置線程池的大小。
一、Java中的ThreadPoolExecutor 類
1、ThreadPoolExecutor
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,因此如果要透徹地了解Java中的線程池,必須先了解這個類。下面我們來看一下ThreadPoolExecutor類的具體實現(xiàn)源碼。
(1)繼承關(guān)系與類聲明
java.lang.Object |____java.util.concurrent.AbstractExecutorService|____java.util.concurrent.ThreadPoolExecutorpackage java.util.concurrent; /*** @since 1.5* @author Doug Lea*/ public class ThreadPoolExecutor extends AbstractExecutorService {
(2)構(gòu)造方法
從代碼上看,ThreadPoolExecutor 繼承了 AbstractExecutorService類,并提供了四個構(gòu)造器,事實上,通過觀察每個構(gòu)造器的實現(xiàn),發(fā)現(xiàn)前面三個構(gòu)造器都是調(diào)用的第四個構(gòu)造器進行初始化工作的。
下面解釋一下構(gòu)造器中各個參數(shù)的含義:
corePoolSize:核心池的大小
這個參數(shù)跟后面講述的線程池的實現(xiàn)原理有非常大的關(guān)系。在創(chuàng)建了線程池后,默認情況下,線程池中并沒有任何線程,而是等待有任務(wù)到來才創(chuàng)建線程去執(zhí)行任務(wù),除非調(diào)用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,從這2個方法的名字就可以看出,是預(yù)創(chuàng)建線程的意思,即在沒有任務(wù)到來之前就創(chuàng)建 corePoolSize 個線程或者一個線程。默認情況下,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0,當(dāng)有任務(wù)來之后,才會創(chuàng)建一個線程去執(zhí)行任務(wù),當(dāng)線程池中的線程數(shù)目達到corePoolSize后,就會把到達的任務(wù)放到緩存隊列當(dāng)中。
maximumPoolSize:線程池最大線程數(shù)
這個參數(shù)也是一個非常重要的參數(shù),它表示在線程池中最多能創(chuàng)建多少個線程。
keepAliveTime:空閑線程存活時間
表示線程沒有任務(wù)執(zhí)行時最多保持多久時間會終止。默認情況下,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時,keepAliveTime才會起作用,直到線程池中的線程數(shù)不大于corePoolSize,即當(dāng)線程池中的線程數(shù)大于corePoolSize時,如果一個線程空閑的時間達到keepAliveTime,則會終止,直到線程池中的線程數(shù)不超過 corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)(允許核心線程超時)方法,在線程池中的線程數(shù)不大于corePoolSize時,keepAliveTime參數(shù)也會起作用,直到線程池中的線程數(shù)為0;
unit:時間單位
參數(shù) keepAliveTime 的時間單位,有7種取值,在TimeUnit類中有7種靜態(tài)屬性:
TimeUnit.DAYS; ? ? ? ? ? ? ? //天 TimeUnit.HOURS; ? ? ? ? ? ? //小時 TimeUnit.MINUTES; ? ? ? ? ? //分鐘 TimeUnit.SECONDS; ? ? ? ? ? //秒 TimeUnit.MILLISECONDS; ? ? ?//毫秒 TimeUnit.MICROSECONDS; ? ? ?//微妙 TimeUnit.NANOSECONDS; ? ? ? //納秒
workQueue:阻塞隊列
一個阻塞隊列,用來存儲等待執(zhí)行的任務(wù),這個參數(shù)的選擇也很重要,會對線程池的運行過程產(chǎn)生重大影響,一般來說,這里的阻塞隊列有以下幾種選擇:
ArrayBlockingQueue; PriorityBlockingQueue LinkedBlockingQueue; SynchronousQueue
ArrayBlockingQueue 和 PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和SynchronousQueue。線程池的排隊策略與BlockingQueue有關(guān)。
threadFactory:線程工廠
主要用來創(chuàng)建線程。
handler:拒絕處理策略
表示當(dāng)拒絕處理任務(wù)時的策略,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。? ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。? ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程) ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)?
2、AbstractExecutorService類
從上面給出的 ThreadPoolExecutor 類的代碼可以知道,ThreadPoolExecutor繼承了AbstractExecutorService,我們來看一下AbstractExecutorService的實現(xiàn):
package java.util.concurrent;/*** @since 1.5* @author Doug Lea*/public abstract class AbstractExecutorService implements ExecutorService {protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {}protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {}public Future<?> submit(Runnable task) {}public <T> Future<T> submit(Runnable task, T result) {}public <T> Future<T> submit(Callable<T> task) {}private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,boolean timed, long nanos)throws InterruptedException, ExecutionException, TimeoutException {}public <T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException {}public <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {}public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException {}public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException {}}
AbstractExecutorService是一個抽象類,它實現(xiàn)了ExecutorService接口。
3、ExecutorService
我們接著看ExecutorService接口的實現(xiàn):
package java.util.concurrent;/*** @since 1.5* @author Doug Lea*/ public interface ExecutorService extends Executor {void shutdown();List<Runnable> shutdownNow();boolean isShutdown();boolean isTerminated();boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);Future<?> submit(Runnable task);<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException;<T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;}
ExecutorService又是繼承了Executor接口,我們看一下Executor接口的實現(xiàn):
4、Executor
Executor接口的實現(xiàn):
?
package java.util.concurrent;/*** @since 1.5* @author Doug Lea*/ public interface Executor {void execute(Runnable command); }
到這里,大家應(yīng)該明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關(guān)系了。
Executor是一個頂層接口,在它里面只聲明了一個方法execute(Runnable),返回值為void,參數(shù)為Runnable類型,從字面意思可以理解,就是用來執(zhí)行傳進去的任務(wù)的;
然后ExecutorService接口繼承了Executor接口,并聲明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象類AbstractExecutorService實現(xiàn)了ExecutorService接口,基本實現(xiàn)了ExecutorService中聲明的所有方法;
然后ThreadPoolExecutor繼承了類AbstractExecutorService。
5、ThreadPoolExecutor類中有幾個非常重要的方法:
(1)execute()方法:
方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現(xiàn),這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務(wù),交由線程池去執(zhí)行。
(2)submit()方法:
方法是在 ExecutorService 中聲明的方法,在 AbstractExecutorService 就已經(jīng)有了具體的實現(xiàn),在 ThreadPoolExecutor 中并沒有對其進行重寫,這個方法也是用來向線程池提交任務(wù)的,但是它和 execute() 方法不同,它能夠返回任務(wù)執(zhí)行的結(jié)果,去看submit()方法的實現(xiàn),會發(fā)現(xiàn)它實際上還是調(diào)用的execute()方法,只不過它利用了 Future 來獲取任務(wù)執(zhí)行結(jié)果。
(3)shutdown()和shutdownNow()方法:
是用來關(guān)閉線程池的。
(4)很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等獲取與線程池相關(guān)屬性的方法,有興趣的朋友可以自行查閱API。
6、使用示例
任務(wù)類:
package basis.stuThreadPool;import java.util.concurrent.TimeUnit;public class MyTask implements Runnable{private int taskNum;public MyTask(int taskNum) {this.taskNum = taskNum;}@Overridepublic void run() {System.out.println("正在執(zhí)行task"+taskNum);try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("task"+taskNum+"執(zhí)行完畢。");} }
?測試類:
執(zhí)行結(jié)果:
正在執(zhí)行task0
線程池中線程數(shù)目:1,隊列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:2,隊列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:3,隊列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:4,隊列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:1,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:2,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:3,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:4,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:6,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:7,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:8,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:9,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
線程池中線程數(shù)目:10,隊列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行完的任務(wù)數(shù)目:0
正在執(zhí)行task1
正在執(zhí)行task2
正在執(zhí)行task3
正在執(zhí)行task4
正在執(zhí)行task10
正在執(zhí)行task11
正在執(zhí)行task12
正在執(zhí)行task13
正在執(zhí)行task14
task0執(zhí)行完畢。
正在執(zhí)行task5
task11執(zhí)行完畢。
task2執(zhí)行完畢。
task4執(zhí)行完畢。
task3執(zhí)行完畢。
task1執(zhí)行完畢。
task10執(zhí)行完畢。
task13執(zhí)行完畢。
task12執(zhí)行完畢。
task14執(zhí)行完畢。
正在執(zhí)行task9
正在執(zhí)行task6
正在執(zhí)行task8
正在執(zhí)行task7
task5執(zhí)行完畢。
task7執(zhí)行完畢。
task8執(zhí)行完畢。
task6執(zhí)行完畢。
task9執(zhí)行完畢。
從執(zhí)行結(jié)果可以看出:
當(dāng)線程池中線程的數(shù)目大于5 (corePoolSize)時,便將任務(wù)放入任務(wù)緩存隊列里面,當(dāng)任務(wù)緩存隊列滿了(capacity = 5)之后,便創(chuàng)建新的線程,線程數(shù)目不能大于 10(maximumPoolSize)的值。該線程池中共創(chuàng)建了10個線程(最開始先創(chuàng)建 5 個,任務(wù)緩存隊列滿了之后再創(chuàng)建 maximumPoolSize-corePoolSize = 5個),另外,任務(wù)緩存隊列中還有 5 個在等待。
如果上面程序中,將for循環(huán)中改成執(zhí)行20個任務(wù),就會拋出任務(wù)拒絕異常了。
不過在java doc中,并不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類來創(chuàng)建線程池。
7、Executors
Executors 是一個線程池工具類,提供了很多靜態(tài)方法,用于簡化線程池的創(chuàng)建和使用。
Executors 中創(chuàng)建線程池的方法:
方法?? ? 描述 newCachedThreadPool()?? ? 創(chuàng)建一個不限容量(最大為Integer.MAX_VALUE)的線程池 newSingleThreadExecutor()?? ? 創(chuàng)建一個單線程的線程池 newFixedThreadExecutor()?? ?創(chuàng)建一個固定容量的線程池
這三個方法的具體實現(xiàn):
?示例(1)newFixedThreadPool:
任務(wù)類:
package basis.stuThreadPool;import java.util.concurrent.TimeUnit;public class MyTask implements Runnable{private int taskNum;public MyTask(int taskNum) {this.taskNum = taskNum;}@Overridepublic void run() {System.out.println("正在執(zhí)行task"+taskNum);try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("task"+taskNum+"執(zhí)行完畢。");} }
測試類:
測試結(jié)果:
正在執(zhí)行task0
正在執(zhí)行task1
正在執(zhí)行task2
正在執(zhí)行task3
正在執(zhí)行task4
task0執(zhí)行完畢。
正在執(zhí)行task5
task1執(zhí)行完畢。
正在執(zhí)行task6
task3執(zhí)行完畢。
task4執(zhí)行完畢。
task2執(zhí)行完畢。
正在執(zhí)行task8
正在執(zhí)行task7
正在執(zhí)行task9
task5執(zhí)行完畢。
task6執(zhí)行完畢。
task8執(zhí)行完畢。
task7執(zhí)行完畢。
task9執(zhí)行完畢。
上述代碼使用Executors創(chuàng)建了一個固定容量為5的線程池。當(dāng)線程池中線程達到最大容量時,不再添加新的線程,其他線程必須等待線程池中的線程執(zhí)行完成后,才能進入線程池中執(zhí)行。
?示例(2)newCachedThreadPool:
修改測試類,把固定容量的線程池改為不限容量的線程池,
//創(chuàng)建一個不限容量的線程池
ExecutorService executor = Executors.newCachedThreadPool();
其他代碼不變,運行測試,結(jié)果如下:
正在執(zhí)行task0
正在執(zhí)行task1
正在執(zhí)行task2
正在執(zhí)行task3
正在執(zhí)行task4
正在執(zhí)行task5
正在執(zhí)行task6
正在執(zhí)行task7
正在執(zhí)行task8
正在執(zhí)行task9
task0執(zhí)行完畢。
task2執(zhí)行完畢。
task6執(zhí)行完畢。
task4執(zhí)行完畢。
task3執(zhí)行完畢。
task9執(zhí)行完畢。
task1執(zhí)行完畢。
task8執(zhí)行完畢。
task7執(zhí)行完畢。
task5執(zhí)行完畢。
newCachedThreadPool方法會創(chuàng)建一個容量自動調(diào)整的線程池,最大容量為Integer.MAXVALUE。每當(dāng)有新的任務(wù),線程池中就加入一個新的線程。
示例(3)newSingleThreadExecutor:
修改測試類,把不限容量的線程池改為單線程的線程池,
//創(chuàng)建一個單線程的線程池
ExecutorService executor = Executors.newSingleThreadExecutor();
其他代碼不變,運行測試,結(jié)果如下:
正在執(zhí)行task0
task0執(zhí)行完畢。
正在執(zhí)行task1
task1執(zhí)行完畢。
正在執(zhí)行task2
task2執(zhí)行完畢。
正在執(zhí)行task3
task3執(zhí)行完畢。
正在執(zhí)行task4
task4執(zhí)行完畢。
正在執(zhí)行task5
task5執(zhí)行完畢。
正在執(zhí)行task6
task6執(zhí)行完畢。
正在執(zhí)行task7
task7執(zhí)行完畢。
正在執(zhí)行task8
task8執(zhí)行完畢。
正在執(zhí)行task9
task9執(zhí)行完畢。
newSingleThreadExecutor方法會創(chuàng)建一個單線程的線程池,一次只能有一個任務(wù)進入線程池執(zhí)行,其他線程只能等待線程池中的那個線程執(zhí)行完畢后才能進入線程池。
總結(jié)
以上是生活随笔為你收集整理的Java:多线程之线程池的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA中return与finally的
- 下一篇: Jsp(Java Server Page