我理解的Java并发基础(七):线程池和Executors工具类
2018-04-15 08:38
761 查看
线程池,顾名思义就是很多线程对象组成的一个组,对外提供执行任务的服务。调用者不必要关心线程的管理方面的细节。
在程序中使用线程池的好处:
1,减少内存消耗。每一个线程对象对应操作系统中的一个线程,频繁创建,占用过多系统内存。
2,提高执行效率。系统创建线程需要时间(win系统耗时,linux可以忽略),线程对象在堆中的初始化也耗时。
3,管理方便。线程池负责线程的管理,并提供简单的管理API。调用者只需要负责执行任务的创建。
一,ThreadPoolExecutor是创建和管理线程池的类,来看看它常用的构造API。
ThreadPoolExecutor 构造方法的这几个参数,也是理解线程池工作的核心概念:
1,int corePoolSize,核心线程数。线程池会努力以corePoolSize个数的线程数来执行交给线程池的任务。在没有任务的时候,核心线程是不会被回收的。
2,BlockingQueue<Runnable> workQueue,任务队列。当线程池执行的任务数超过核心线程数的时候,即核心线程满载的情况下,新接受的任务将被存放到该任务队列中。一般情况下推荐使用阻塞队列。
workQueue这里支持的几个实现类:ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue等。
3,int maximumPoolSize,最大线程数。如果线程池接受的任务依然在增加,任务队列workQueue满了之后(无界队列不会满),会开始创建新的线程来执行任务,直到线程池的总线程数达到maximumPlloSize的大小。
4,RejectedExecutionHandler handler,拒绝策略。当线程池已经达到了最大线程数,任务队列workQueue也已经满了的情况下,线程池已经没有能力再处理新的任务了,即线程池处于饱和状态。如果此时依然有新的任务提交给线程池,则会采取拒绝策略来执行。RejectedExecutionHandler接口就一个方法需要实现,即
4.1,AbortPolicy,直接抛出异常。
4.2,CallerRunsPolicy,使用任务提交者所在线程执行任务。
4.3,DiscardOldestPolicy,丢弃任务队列中的头任务,把新任务添加到队列尾部。
4.4,DiscardPolicy,直接丢弃,不会抛异常。
当然,开发者也可以自己实现RejectedExecutionHandler接口来达到自定义的拒绝策略。
5,ThreadFactory threadFactory 创建线程的工程。ThreadFactory接口就一个方法
6,long keepAliveTime 和 TimeUnit unit,空闲时间。当线程池的工作线程空闲一段时间之后,超过核心线程数的线程将会被抛弃。JDK1.6之后,如果显示调用了
以上的这些构造方法的参数都有对应的public类型的get和set方法。
线程池对象在创建后,默认执行任务的时候才开始一个一个的创建核心线程,知道累计执行了corePoolSize个任务之后才完成了核心线程的初始化。调用
ThreadPoolExecutor 执行任务的API:
ThreadPoolExecutor接受两种类型的参数来执行任务。一种是Runnable接口类型,一种是Callable类型。后者可以返回一个Future对象,通过future对象的get方法可以获取该任务的执行结果。
二,监控、关闭和扩展线程池的API:
三、合理配置线程池。根据任务的特性,选择合适的构造参数来构造线程池。
1,如果是CPU密集型的任务,配置较小的线程数。如:线程池核心数=CPU核心数+1;
2,如果是IO密集型的任务,可以配置较大的线程数。如:线程池核心数=CPU核心数*2;
3,混合型的任务可以尝试分拆为CPU密集型和IO密集型,再使用不同的线程池来处理;
4,可以通过Runtime.getRuntime().availableProcessors()获取当前设备的CPU核心数。
四、Executors工具类
jdk提供的Executors工具类 封装了一些常用的线程池使用场景类。
1,Runnable和Callable的区别与联系。
二者都是封装了任务的对象类。前者可以通过线程池的
2,通过Executors工具类创建FixedThreadPool。
FixedThreadPool线程池属于ThreadPoolExecutor的一种。核心线程数固定且等于最大线程数,队列采用LinkedBlockingQueue。该线程池的特点是采用固定大小数量的线程来处理任务,任务队列顺序执行,几乎无界。参见其构造方法:
3,通过Executors工具类创建SingleThreadExecutor。
SingleThreadExecutor线程池属于ThreadPoolExecutor的一种。核心线程数=最大线程数=1,队列采用LinkedBlockingQueue。该线程池的特点是采用单线程来处理任务,避免并发冲突。任务队列顺序执行,几乎无界 。参见其构造方法:
4,通过Executors工具类创建CachedThreadPool。
CachedThreadPool线程池属于ThreadPoolExecutor的一种。核心线程数=0,最大线程数=Integer.MAX_VALUE,队列采用SynchronousQueue。该线程池的特点是队列不存任务,每个任务都有单独的线程执行(全量并发)。参见其构造方法:
5,ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,用来执行周期性或定时任务。参见其构造方法:
特殊指出在于队列采用了DelayedWorkQueue,而DelayedWorkQueue是ScheduledThreadPoolExecutor的内部实现类,具有DelayQueue和PriorityQueue的特性。DelayQueue用于实现定时任务,结合PriorityQueue按time进行排序可以实现整体任务的定时特性。
6,通过Executors工具类创建ScheduledThreadPool。
ScheduledThreadPool 线程池属于ScheduledThreadPoolExecutor 的一种。核心线程数固定,提高任务执行效率。参见其构造方法:
7,通过Executors工具类创建SingleThreadScheduledExecutor。
SingleThreadScheduledExecutor线程池属于ScheduledThreadPoolExecutor 的一种。核心线程数=1,避免并发冲突。参见其构造方法:
tips:
首选Executor的cachedThreadPool,只有任务数瞬时过大或者这种方式引发性能问题时,才切换为FixedThreadPool。SingleThreadExecutor是线程数量为1的FixedThreadPool,遇到多任务时会排队执行。
在程序中使用线程池的好处:
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,遇到多任务时会排队执行。
相关文章推荐
- 我理解的Java并发基础(五):并发工具类和ThreadLocal
- Java并发基础(六) - 线程池
- Java并发---- Executor并发框架--线程池,ThreadToolExecutor初步理解
- Java基础 :反射、注解、代理、线程池、依赖的学习和理解
- 【死磕Java并发】-----J.U.C之线程池:线程池的基础架构
- JAVA并发编程:线程池Executors
- Java并发编程之深入理解线程池原理及实现
- Java 并发:Executors 和线程池
- Java 并发:Executors 和线程池
- Java 并发Executors 和线程池
- JAVA CONCURRENCY EXECUTORS 介绍Java并发处理线程池
- Java 并发:Executors 和线程池
- Java基础学习总结(104)——多线程、并发、工具类相关的面试题
- 我理解的Java并发基础(二):happens-before、可见性与原子性
- Java线程总结(五):并发包------线程池Executors
- Java并发编程之线程池的理解与使用
- Java 并发:Executors 和线程池
- 【转】Java 并发:Executors 和线程池
- 黑马程序员:Java基础总结----线程池及java5的线程并发库
- 我理解的Java并发基础(一):一些基本概念