Java中線程池的實現
當我們要使用線程時,就要去創建線程,這樣看上去似乎很正常,但是頻繁地創建線程、銷燬線程會對系統資源造成不小地負擔,大大降低了系統地效率。
Java中有一個很好地工具,可以事先創建好一些線程,當我們需要使用地時候就直接拿來使用就可以,這樣就不會頻繁地創建和銷燬線程了,這個神奇地工具就是線程池。
線程池是通過Executor執行器來創建。
我們來看一下在java.util.concurrent包下面的Executor類都有哪些東西。
Executor提供的幾種創建線程池的方法:
1.newFixedThreadPool
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
簡單翻譯一下就是說這個類創建了些個固定數目的共享線程,即實現規定好創建的線程的數量,後面不能更改數量。
在任何一個時間點,活躍工作的線程數量都不能夠超過這個最大的數量。
當所有的線程都在工作時,如果這時候有一個新的線程需要被創建,那麼這個需要創建的線程需要就會被放進一個等待隊列BlockingQueue,直到有空餘的線程可以使用才執行。
如果有一個線程在執行期間由於發生了錯誤而終止運行,一個新的線程會接替這個終止的線程完成就下來的任務。
線程池中的線程們會一直存在直到被要求關閉。
初始化時指定nThreads線程池線程數量上限。
2.newWorkStealingPool:
/**
* Creates a thread pool that maintains enough threads to support
* the given parallelism level, and may use multiple queues to
* reduce contention. The parallelism level corresponds to the
* maximum number of threads actively engaged in, or available to
* engage in, task processing. The actual number of threads may
* grow and shrink dynamically. A work-stealing pool makes no
* guarantees about the order in which submitted tasks are
* executed.
*
* @param parallelism the targeted parallelism level
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code parallelism <= 0}
* @since 1.8
*/
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
parallelism爲並行度,newWorkStealingPool也有一個不帶參數的構造,默認並行度爲cup的個數。
這個參數規定了同一時間同時執行任務的線程數量上限。平時線程的數量是會動態增加或減少的,並不像newFixedThreadPool那樣是固定的線程數量。
同時也不保證按照任務提交的順序來完成任務,它爲了減少競爭,採取了多種的queue。
3.newCachedThreadPool
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
創建一個可以動態增長的線程池,當我們有新的任務提交時,但是當前線程池並沒有空餘的線程可以供我們使用,這時候就會新建一個線程,如果有以前創建的線程空閒下來,則不會創建新的線程而是使用以前已經創建的。
當一個線程空閒下來超過60秒,這個線程就會被從緩存線程池中移除。
這種緩存線程池在應對那些執行時間很短的異步任務時表現很好。
4.newScheduledThreadPool
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @param threadFactory the factory to use when the executor
* creates a new thread
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
* @throws NullPointerException if threadFactory is null
*/
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
定時週期性地執行任務,線程數量固定。
public class Main {
public static void main(String[] args) throws Exception {
// 指定大小爲4
ScheduledExecutorService m = Executors.newScheduledThreadPool(4);
m.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Date now = new Date();
System.out.println("線程" + Thread.currentThread() + "報時:" + now);
}
}, 1, 1, TimeUnit.SECONDS); // 延遲1s秒執行,每隔1s執行一次
}
}
5.newSingleThreadPool:
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
顧名思義,這個線程池裏只有一個線程,任何一個時刻,最多隻有一個線程在工作。
每個被提交地任務按照先提交先執行的原則依次執行。
當原來的線程出現問題被終止了,會有一個新的線程被創建去執行後面的任務。
這個線程的阻塞隊列採用了無邊界的LinkedBlockingQueue。
還有一些擴展的比如
newSingleThreadScheduledExecutor
就是在上面一種的情況下加入了計時週期性執行。
這些就是Executor類提供給我們的創建線程池的方法,我們可以根據業務需要選擇合適的方法。
這些線程池爲了效率和應對特殊的情況,採用了不同的BlockingQueue,下面一篇文章我會介紹一下各種
BlockingQueue的實現類。