Java线程池
并发编程的一种编程方式是把任务拆分为一系列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe)。Executor在执行时使用内部的线程池完成操作。
1. 为什么要使用线程池
- 每个线程可用重复利用,能够减少创建和销毁线程的次数,减少系统开销。
- 可以根据系统承载力,调整线程池中工作线程的数目,防止过多消耗内存。
2. Java中的线程池框架
- 顶级接口:Executor,严格说Executor并不是一个线程池,而是执行线程的工具。真正的线程池接口是ExecutorService。
- ExecutorService:真正的线程池接口。
- ScheduledExecutorService:提供重复执行、延迟执行功能的线程池接口。
- ThreadPoolExecutor:ExecutorService的默认实现。
- ScheduledThreadPoolExecutor:继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
3. 线程池构造函数的各属性说明
-
核心池的大小,即线程池中允许空闲的线程数量。
-
maximumPoolSize:线程池允许的最大线程数。在创建线程后线程池中线程数量为0,当有任务到来时创建一个线程去执行。当线程池中线程数量为corePoolSize时,将任务加入缓存队列,若入队失败(队列已满),则尝试创建临时线程,但临时+核心线程总数不能超过maximumPoolSize,当线程总数达到后会拒绝新任务。因此有两种方式可以让任务不被拒绝:① 将maximumPoolSize设为Integer.MAX_VALUE(线程不可能达到这个值),CachedThreadPool即是如此;② 使用无限容量的阻塞队列(比如LinkedBlockingQueue),FixedThreadPool即是如此。
-
keepAliveTime:表示线程没有任务执行时最多保持多久时间。默认只有当线程池中线程数大于corePoolSize时,keepAliveTime才会起作用。但是如果调用了allowCoreThreadTimeOut(true)方法,在线程池中线程数不大于corePoolSize时,keepAliveTime也会起作用,直到线程数为0。
-
unit:参数keepAliveTime的单位,有7种取值:
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微秒 TimeUnit.NANOSECONDS; //纳秒
-
workQueue:阻塞队列(BlockingQueue接口的实现类),用来存储等待执行的任务,只保留execute方法提交的Runnable任务。一般有以下几种选择:
- ArrayBlockingQueue:数组实现的阻塞队列,不支持扩容,队列满了会根据handler参数中指定的策略拒绝任务。
- LinkedBlockingQueue:链表实现的阻塞队列,默认容量Integer.MAX_VALUE,也可以通过构造方法限制容量。
- SynchronousQueue:零容量的同步阻塞队列,直到有线程接受任务才返回,用于生产者消费者同步问题,被称为同步队列。
- PriorityBlockingQueue:二插堆实现的优先级阻塞队列。
- DelayQueue:延时阻塞队列,队列中所有的任务都会封装成ScheduledFutureTask对象。
-
threadFactory:线程工程,主要用来创建线程;默认使用Executors工具类定义的DefaultThreadFactory。可以实现ThreadFactory接口来自己控制创建线程的过程。
-
handler:表示拒绝任务时的策略,有以下四种取值(默认为AbortPolicy):
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
4. 创建线程池
配置线程池较为复杂,Executors(注意这个有s不是Executor接口)类中提供了一些静态工厂用于创建常用的线程池。
- newSingleThreadExecutor:创建一个单线程的线程池。能保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool:创建固定大小(corePoolSize=maximumPoolSize)的线程,拥有不限制大小的阻塞队列(LinkedBlockingQueue)。每次提交一个任务就创建一个线程,直到线程数量达到corePoolSize,之后的任务全部加入阻塞队列。
- newCachedThreadPool:创建核心线程数为0,不限制大小,临时线程60s回收的线程池,线程池大小依赖于操作系统(或JVM)能够创建的最大线程大小,使用同步队列,将任务直接提交给线程。
- newScheduledThreadPool:创建一个大小无限,临时线程用完就回收的线程池。此线程池支持定时及周期性执行任务的需求。
5. 任务提交的三种方式
- execute(Runnable command):定义在Executor接口中。
- submit的三个重载方法:定义在ExecutorService接口中。
- invoke(invokeAll,invokeAny)提交方式:定义在ExecutorService接口中
submit最终也是调用execute去执行,但是submit方法返回一个FutureTask对象,可以通过FutureTask对象获取任务执行的结果,
6. 更多种类的线程池 MoreExecutors
略,详解见此文档