Thread Pool簡介
在Java中,threads是和系統的threads相對應的,用來處理一系列的系統資源。不管在windows和linux下面,能開啓的線程個數都是有限的,如果你在java程序中無限制的創建thread,那麼將會遇到無線程可創建的情況。
CPU的核數是有限的,如果同時有多個線程正在運行中,那麼CPU將會根據線程的優先級進行輪循,給每個線程分配特定的CPU時間。所以線程也不是越多越好。
在java中,代表管理ThreadPool的接口有兩個:ExecutorService和Executor。
我們運行線程的步驟一般是這樣的:1. 創建一個ExecutorService。2.將任務提交給ExecutorService。3.ExecutorService調度線程來運行任務。
畫個圖來表示:
threadPool.png
下面我講一下,怎麼在java中使用ThreadPool。
Executors, Executor 和 ExecutorService
Executors 提供了一系列簡便的方法,來幫助我們創建ThreadPool。
Executor接口定義了一個方法:
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
ExecutorService繼承了Executor,提供了更多的線程池的操作。是對Executor的補充。
根據接口實現分離的原則,我們通常在java代碼中使用ExecutorService或者Executor,而不是具體的實現類。
我們看下怎麼通過Executors來創建一個Executor和ExecutorService:
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> log.info("in Executor"));
ExecutorService executorService= Executors.newCachedThreadPool();
executorService.submit(()->log.info("in ExecutorService"));
executorService.shutdown();
關於ExecutorService的細節,我們這裏就多講了,感興趣的朋友可以參考之前我寫的ExecutorService的詳細文章。
ThreadPoolExecutor
ThreadPoolExecutor是ExecutorService接口的一個實現,它可以爲線程池添加更加精細的配置,具體而言它可以控制這三個參數:corePoolSize, maximumPoolSize, 和 keepAliveTime。
PoolSize就是線程池裏面的線程個數,corePoolSize表示的是線程池裏面初始化和保持的最小的線程個數。
如果當前等待線程太多,可以設置maximumPoolSize來提供最大的線程池個數,從而線程池會創建更多的線程以供任務執行。
keepAliveTime是多餘的線程未分配任務將會等待的時間。超出該時間,線程將會被線程池回收。
我們看下怎麼創建一個ThreadPoolExecutor:
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
threadPoolExecutor.submit(()->log.info("submit through threadPoolExecutor"));
threadPoolExecutor.shutdown();
上面的例子中我們通過ThreadPoolExecutor的構造函數來創建ThreadPoolExecutor。
通常來說Executors已經內置了ThreadPoolExecutor的很多實現,我們來看下面的例子:
ThreadPoolExecutor executor1 =
(ThreadPoolExecutor) Executors.newFixedThreadPool(2);
executor1.submit(() -> {
Thread.sleep(1000);
return null;
});
executor1.submit(() -> {
Thread.sleep(1000);
return null;
});
executor1.submit(() -> {
Thread.sleep(1000);
return null;
});
log.info("executor1 poolsize {}",executor1.getPoolSize());
log.info("executor1 queuesize {}", executor1.getQueue().size());
executor1.shutdown();
上的例子中我們Executors.newFixedThreadPool(2)來創建一個ThreadPoolExecutor。
上面的例子中我們提交了3個task。但是我們pool size只有2。所以還有一個1個不能立刻被執行,需要在queue中等待。
我們再看一個例子:
ThreadPoolExecutor executor2 =
(ThreadPoolExecutor) Executors.newCachedThreadPool();
executor2.submit(() -> {
Thread.sleep(1000);
return null;
});
executor2.submit(() -> {
Thread.sleep(1000);
return null;
});
executor2.submit(() -> {
Thread.sleep(1000);
return null;
});
log.info("executor2 poolsize {}", executor2.getPoolSize());
log.info("executor2 queue size {}", executor2.getQueue().size());
executor2.shutdown();
上面的例子中我們使用Executors.newCachedThreadPool()來創建一個ThreadPoolExecutor。運行之後我們可以看到poolsize是3,而queue size是0。這表明newCachedThreadPool會自動增加pool size。
如果thread在60秒鐘之類沒有被激活,則會被收回。
這裏的Queue是一個SynchronousQueue,因爲插入和取出基本上是同時進行的,所以這裏的queue size基本都是0.
ScheduledThreadPoolExecutor
還有個很常用的ScheduledThreadPoolExecutor,它繼承自ThreadPoolExecutor, 並且實現了ScheduledExecutorService接口。
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService
我們看下怎麼使用:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
executor.schedule(() -> {
log.info("Hello World");
}, 500, TimeUnit.MILLISECONDS);
上面的例子中,我們定義了一個定時任務將會在500毫秒之後執行。
之前我們也講到了ScheduledExecutorService還有兩個非常常用的方法:
- scheduleAtFixedRate - 以開始時間爲間隔。
- scheduleWithFixedDelay - 以結束時間爲間隔。
CountDownLatch lock = new CountDownLatch(3);
ScheduledExecutorService executor2 = Executors.newScheduledThreadPool(5);
ScheduledFuture<?> future = executor2.scheduleAtFixedRate(() -> {
log.info("in ScheduledFuture");
lock.countDown();
}, 500, 100, TimeUnit.MILLISECONDS);
lock.await(1000, TimeUnit.MILLISECONDS);
future.cancel(true);
ForkJoinPool
ForkJoinPool是在java 7 中引入的新框架,我們將會在後面的文章中詳細講解。這裏做個簡單的介紹。
ForkJoinPool主要用來生成大量的任務來做算法運算。如果用線程來做的話,會消耗大量的線程。但是在fork/join框架中就不會出現這個問題。
在fork/join中,任何task都可以生成大量的子task,然後通過使用join()等待子task結束。
這裏我們舉一個例子:
static class TreeNode {
int value;
Set<TreeNode> children;
TreeNode(int value, TreeNode... children) {
this.value = value;
this.children = Sets.newHashSet(children);
}
}
定義一個TreeNode,然後遍歷所有的value,將其加起來:
public class CountingTask extends RecursiveTask<Integer> {
private final TreeNode node;
public CountingTask(TreeNode node) {
this.node = node;
}
@Override
protected Integer compute() {
return node.value + node.children.stream()
.map(childNode -> new CountingTask(childNode).fork()).mapToInt(ForkJoinTask::join).sum();
}
}
下面是調用的代碼:
public static void main(String[] args) {
TreeNode tree = new TreeNode(5,
new TreeNode(3), new TreeNode(2,
new TreeNode(2), new TreeNode(8)));
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
int sum = forkJoinPool.invoke(new CountingTask(tree));
}