JAVA多執行緒之——常用執行緒池
執行緒池
學習了執行緒池的基本原理後,可以理解執行緒池的型別控制,主要是通過中心池大小,和最大執行緒池大小,以及儲存工作任務的佇列決定。JDK中為我們封裝了常用的四種執行緒池。
在JDK幫助文件中,有如此一段話:
“強烈建議程式設計師使用較為方便的Executors工廠方法Executors.newCachedThreadPool()(無界執行緒池,可以進行自動執行緒回收)、Executors.newFixedThreadPool(int)(固定大小執行緒池)Executors.newSingleThreadExecutor()(單個後臺執行緒)。
學習常用執行緒池,關鍵點就在於理解執行緒池大小與各種佇列的搭配。
回顧執行緒吃的基本工作流程
如果執行的執行緒少於 corePoolSize,則 Executor始終首選新增新的執行緒,而不進行排隊。(如果當前執行的執行緒小於corePoolSize,則任務根本不會存放,新增到queue中,而是直接抄傢伙(thread)開始執行)
如果執行的執行緒等於或多於 corePoolSize,則 Executor始終首選將請求加入佇列,而不新增新的執行緒。
如果無法將請求加入佇列,則建立新的執行緒,除非建立此執行緒超出 maximumPoolSize,在這種情況下,任務將被拒絕。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
此執行緒池通過設定maximumPoolSize為最大整型值,SynchronousQueue是一個無界佇列,這樣就可以無限建立執行緒,通過設定執行緒空閒keepAliveTime 時間為60秒,這就達到一個快取的效果。使用SynchronousQueue佇列,基於它的另外一個特性,該佇列每次只能有一個元素,這樣,當佇列中有一個元素在等待的時候,後續的新的任務,無法加入佇列,就可以直接通過建立新的執行緒進行執行,不需要在佇列中等待執行。
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
Executors.newSingleThreadExecutor();
ExecutorService pool = Executors.newCachedThreadPool();
List<Mycommand> commands = new ArrayList<Mycommand>();
Mycommand command = null;
for(int i = 0; i < 10; i++) {
command = new Mycommand();
commands.add(command);
}
for(Mycommand myommand : commands) {
pool.execute(myommand);
}
}
static class Mycommand implements Runnable{
@Override
public void run() {
System.out.println("Mycommand-" + Thread.currentThread().getName() + "我執行拉。。。");
}
}
執行結果:
Mycommand-pool-2-thread-1我執行拉。。。
Mycommand-pool-2-thread-2我執行拉。。。
Mycommand-pool-2-thread-1我執行拉。。。
Mycommand-pool-2-thread-3我執行拉。。。
Mycommand-pool-2-thread-3我執行拉。。。
Mycommand-pool-2-thread-1我執行拉。。。
Mycommand-pool-2-thread-4我執行拉。。。
Mycommand-pool-2-thread-7我執行拉。。。
Mycommand-pool-2-thread-6我執行拉。。。
Mycommand-pool-2-thread-5我執行拉。。。
結果可以看到,其中有多個thread-1在執行,這說明執行緒是被重用了。快取執行緒池,使用於處理速度大於執行緒提交速度。因為它是無界的,也就是說無限大,如果提交速度大於處理速度,那麼就會一直創造執行緒,可能造成資源耗盡。
newSingleThreadExecutor
public class ThreadPoolExecutorTest {
static AtomicInteger index = new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();
List<Mycommand> commands = new ArrayList<Mycommand>();
Mycommand command = null;
for(int i = 0; i < 10; i++) {
command = new Mycommand();
commands.add(command);
}
for(Mycommand myommand : commands) {
pool.execute(myommand);
}
}
static class Mycommand implements Runnable{
@Override
public void run() {
System.out.println("Mycommand-" + index.getAndIncrement() + "-" + Thread.currentThread().getName() + "我執行拉。。。");
}
}
}
執行結果:
Mycommand-0-pool-1-thread-1我執行拉。。。
Mycommand-1-pool-1-thread-1我執行拉。。。
Mycommand-2-pool-1-thread-1我執行拉。。。
Mycommand-3-pool-1-thread-1我執行拉。。。
Mycommand-4-pool-1-thread-1我執行拉。。。
Mycommand-5-pool-1-thread-1我執行拉。。。
Mycommand-6-pool-1-thread-1我執行拉。。。
Mycommand-7-pool-1-thread-1我執行拉。。。
Mycommand-8-pool-1-thread-1我執行拉。。。
Mycommand-9-pool-1-thread-1我執行拉。。。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
理解了單一執行緒池,那麼固定大小的執行緒池就不難理解了,只需要修改corePoolSize與maximumPoolSize的大小並且保證相等就可以了。但一執行緒池就是一個大小為1的固定執行緒池。
public class ThreadPoolExecutorTest {
static AtomicInteger index = new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
List<Mycommand> commands = new ArrayList<Mycommand>();
Mycommand command = null;
for(int i = 0; i < 10; i++) {
command = new Mycommand();
commands.add(command);
}
for(Mycommand myommand : commands) {
pool.execute(myommand);
}
}
static class Mycommand implements Runnable{
@Override
public void run() {
System.out.println("Mycommand-" + index.getAndIncrement() + "-" + Thread.currentThread().getName() + "我執行拉。。。");
}
}
}
某次執行結果:
Mycommand-0-pool-1-thread-1我執行拉。。。
Mycommand-1-pool-1-thread-2我執行拉。。。
Mycommand-2-pool-1-thread-1我執行拉。。。
Mycommand-3-pool-1-thread-2我執行拉。。。
Mycommand-4-pool-1-thread-1我執行拉。。。
Mycommand-5-pool-1-thread-2我執行拉。。。
Mycommand-6-pool-1-thread-1我執行拉。。。
Mycommand-8-pool-1-thread-1我執行拉。。。
Mycommand-7-pool-1-thread-2我執行拉。。。
Mycommand-9-pool-1-thread-1我執行拉。。。
newScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
延遲執行緒池,都瞭解過JAVA的timer。這個就是一個優化的延遲執行的執行緒池。一般用於定時任務。
public class ThreadPoolExecutorTest {
static AtomicInteger index = new AtomicInteger(0);
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
List<Mycommand> commands = new ArrayList<Mycommand>();
Mycommand command = new Mycommand();
pool.scheduleAtFixedRate(command, 1000, 2000, TimeUnit.MILLISECONDS);
}
static class Mycommand implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
System.out.println("Mycommand-" + index.getAndIncrement() + "-" + Thread.currentThread().getName() + "我執行拉。。。");
}
}
}
執行結果:
1491629176413
Mycommand-0-pool-1-thread-1我執行拉。。。
1491629178413
Mycommand-1-pool-1-thread-1我執行拉。。。
1491629180413
Mycommand-2-pool-1-thread-1我執行拉。。。
1491629182414
Mycommand-3-pool-1-thread-1我執行拉。。。
1491629184414
Mycommand-4-pool-1-thread-1我執行拉。。。
1491629186414
Mycommand-5-pool-1-thread-1我執行拉。。。
1491629188414
Mycommand-6-pool-1-thread-1我執行拉。。。
1491629190414
Mycommand-7-pool-1-thread-1我執行拉。。。
1491629192414
Mycommand-8-pool-1-thread-1我執行拉。。。
傳統的timer的缺點:Timer對任務的排程是基於絕對時間的;所有的TimerTask只有一個執行緒TimerThread來執行,因此同一時刻只有一個TimerTask在執行;任何一個TimerTask的執行異常都會導致Timer終止所有任務;由於基於絕對時間並且是單執行緒執行,因此在多個任務排程時,長時間執行的任務被執行後有可能導致短時間任務快速在短時間內被執行多次或者乾脆丟棄多個任務。
newScheduledThreadPool功能比Timer更加強大,效能也更加好。這裡至列舉了其中的一個方法。具體可以參照API進行配置執行。