您的位置:首页 > 编程语言 > Java开发

我理解的Java并发基础(七):线程池和Executors工具类

2018-04-15 08:38 761 查看
线程池,顾名思义就是很多线程对象组成的一个组,对外提供执行任务的服务。调用者不必要关心线程的管理方面的细节。
在程序中使用线程池的好处:
1,减少内存消耗。每一个线程对象对应操作系统中的一个线程,频繁创建,占用过多系统内存。
2,提高执行效率。系统创建线程需要时间(win系统耗时,linux可以忽略),线程对象在堆中的初始化也耗时。
3,管理方便。线程池负责线程的管理,并提供简单的管理API。调用者只需要负责执行任务的创建。

一,ThreadPoolExecutor是创建和管理线程池的类,来看看它常用的构造API。

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

ThreadPoolExecutor 构造方法的这几个参数,也是理解线程池工作的核心概念:

1,int corePoolSize,核心线程数。线程池会努力以corePoolSize个数的线程数来执行交给线程池的任务。在没有任务的时候,核心线程是不会被回收的。

2,BlockingQueue<Runnable> workQueue,任务队列。当线程池执行的任务数超过核心线程数的时候,即核心线程满载的情况下,新接受的任务将被存放到该任务队列中。一般情况下推荐使用阻塞队列。
workQueue这里支持的几个实现类:ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue等。

3,int maximumPoolSize,最大线程数。如果线程池接受的任务依然在增加,任务队列workQueue满了之后(无界队列不会满),会开始创建新的线程来执行任务,直到线程池的总线程数达到maximumPlloSize的大小。

4,RejectedExecutionHandler handler,拒绝策略。当线程池已经达到了最大线程数,任务队列workQueue也已经满了的情况下,线程池已经没有能力再处理新的任务了,即线程池处于饱和状态。如果此时依然有新的任务提交给线程池,则会采取拒绝策略来执行。RejectedExecutionHandler接口就一个方法需要实现,即
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
。JDK目前实现RejectedExecutionHandler接口的拒绝策略有4种:
4.1,AbortPolicy,直接抛出异常。
4.2,CallerRunsPolicy,使用任务提交者所在线程执行任务。
4.3,DiscardOldestPolicy,丢弃任务队列中的头任务,把新任务添加到队列尾部。
4.4,DiscardPolicy,直接丢弃,不会抛异常。
当然,开发者也可以自己实现RejectedExecutionHandler接口来达到自定义的拒绝策略。

5,ThreadFactory threadFactory 创建线程的工程。ThreadFactory接口就一个方法
Thread newThread(Runnable r)
。J.U.C包下就一个实现类DefaultThreadFactory,该类也是threadFactory这个参数如果不指定的时候的默认策略。

6,long keepAliveTime 和 TimeUnit unit,空闲时间。当线程池的工作线程空闲一段时间之后,超过核心线程数的线程将会被抛弃。JDK1.6之后,如果显示调用了
public void allowCoreThreadTimeOut(boolean value)
这个方法,则核心线程数在一定时间内没有任务执行的时候,也可以被抛弃。

以上的这些构造方法的参数都有对应的public类型的get和set方法。

线程池对象在创建后,默认执行任务的时候才开始一个一个的创建核心线程,知道累计执行了corePoolSize个任务之后才完成了核心线程的初始化。调用
prestartAllCoreThreads()
后可以提前完成核心线程的初始化工作。

ThreadPoolExecutor 执行任务的API:

public void execute(Runnable command)

public <T> Future<T> submit(Callable<T> task)

ThreadPoolExecutor接受两种类型的参数来执行任务。一种是Runnable接口类型,一种是Callable类型。后者可以返回一个Future对象,通过future对象的get方法可以获取该任务的执行结果。

二,监控、关闭和扩展线程池的API:

public void shutdown(); // 关闭线程池。设置每个正在执行任务的线程的状态interrupt中断标识。所以无法相应中断表示的任务可能永远无法终止。

public List<Runnable> shutdownNow(); // 关闭线程池,并返回workQueue中的Runnable对象的List。

public boolean isShutdown(); // 调用shutdown()或者shutdownNow()方法后,isShutdown()返回true。

public boolean isTerminated(); // 调用shutdown()或者shutdownNow()方法后,所有任务都已关闭后,isTerminated()返回true。

public boolean remove(Runnable task); // 如果task在workQueue中,则移除。

public void purge(); // Callable类型的任务返回的Future类型的对象可以调用future.cancell()来取消执行。调用purge()用来清理workQueue任务队列中已经被cancell的任务。

public int getPoolSize(); // 获取当前线程池中的线程数量。

public int getActiveCount(); // 获取正在执行任务的线程数。

public int getLargestPoolSize(); // 获取线程池中曾经创建过的最大线程数量。

public long getTaskCount(); // 获取线程池中的任务数量,包括正在被执行的线程。

public long getCompletedTaskCount(); // 线程已经执行完了任务,但是还没有执行新的任务,比如正在执行afterExecute()方法,获取处于这个状态的线程的个数。

protected void beforeExecute(Thread t, Runnable r); // 供开发者扩展的方法,在任务执行之前会调用此方法。

protected void afterExecute(Runnable r, Throwable t); // 供开发者扩展的方法,在任务执行完成之后会调用此方法。

protected void terminated(); // 供开发者扩展的方法,线程池关闭之前会调用此方法。

三、合理配置线程池。根据任务的特性,选择合适的构造参数来构造线程池。

1,如果是CPU密集型的任务,配置较小的线程数。如:线程池核心数=CPU核心数+1;
2,如果是IO密集型的任务,可以配置较大的线程数。如:线程池核心数=CPU核心数*2;
3,混合型的任务可以尝试分拆为CPU密集型和IO密集型,再使用不同的线程池来处理;
4,可以通过Runtime.getRuntime().availableProcessors()获取当前设备的CPU核心数。

四、Executors工具类
jdk提供的Executors工具类 封装了一些常用的线程池使用场景类。

1,Runnable和Callable的区别与联系。
二者都是封装了任务的对象类。前者可以通过线程池的
void execute(Runnable command)
来执行,返回值为void;后者只能够通过线程池的
<T> Future<T> submit(Callable<T> task)
来执行,返回future对象通过调用get()方法组设获取返回值或者future的其他方法完成更多操作。

2,通过Executors工具类创建FixedThreadPool。
FixedThreadPool线程池属于ThreadPoolExecutor的一种。核心线程数固定且等于最大线程数,队列采用LinkedBlockingQueue。该线程池的特点是采用固定大小数量的线程来处理任务,任务队列顺序执行,几乎无界。参见其构造方法:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

3,通过Executors工具类创建SingleThreadExecutor。
SingleThreadExecutor线程池属于ThreadPoolExecutor的一种。核心线程数=最大线程数=1,队列采用LinkedBlockingQueue。该线程池的特点是采用单线程来处理任务,避免并发冲突。任务队列顺序执行,几乎无界 。参见其构造方法:

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

4,通过Executors工具类创建CachedThreadPool。
CachedThreadPool线程池属于ThreadPoolExecutor的一种。核心线程数=0,最大线程数=Integer.MAX_VALUE,队列采用SynchronousQueue。该线程池的特点是队列不存任务,每个任务都有单独的线程执行(全量并发)。参见其构造方法:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

5,ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,用来执行周期性或定时任务。参见其构造方法:

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}

特殊指出在于队列采用了DelayedWorkQueue,而DelayedWorkQueue是ScheduledThreadPoolExecutor的内部实现类,具有DelayQueue和PriorityQueue的特性。DelayQueue用于实现定时任务,结合PriorityQueue按time进行排序可以实现整体任务的定时特性。

6,通过Executors工具类创建ScheduledThreadPool。
ScheduledThreadPool 线程池属于ScheduledThreadPoolExecutor 的一种。核心线程数固定,提高任务执行效率。参见其构造方法:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

7,通过Executors工具类创建SingleThreadScheduledExecutor。
SingleThreadScheduledExecutor线程池属于ScheduledThreadPoolExecutor 的一种。核心线程数=1,避免并发冲突。参见其构造方法:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}

tips:
首选Executor的cachedThreadPool,只有任务数瞬时过大或者这种方式引发性能问题时,才切换为FixedThreadPool。SingleThreadExecutor是线程数量为1的FixedThreadPool,遇到多任务时会排队执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: