Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。
使用线程池的好处。
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
线程池的实现原理
线程池的处理流程
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
ThreadPoolExecutor执行Executor方法分下面4种情况
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(即使其他基本空闲的基本线程能够执行新任务也会创建,因为,空闲线程只会去搜索任务队列中的任务去执行;注意,执行这一步骤需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
5)特别注意, 在 corePoolSize 和 maximumPoolSize 之间的线程数会被自动释放。 当线程池中线程数量大于 corePoolSize 时, 如果某线程空闲时间超过 keepAliveTime, 线程将被终止, 直至线程池中的线程数目不大于 corePoolSize。 这样, 线程池可以动态的调整池中的线程数。
public void excute(Runnable command){
if(commond == null)
throw new NullPointerException();
if(poolsize>=corePoolSize || !addIfUnderCorePoolSize(command){
if(runstate == RUNNING && workQueue.offer(command)){//②
if(*runstate!=RUNNING || poolSize==0)
ensureQUeueTaskHandled(command);
}
else if(!addIfUnderMaximumPoolSize(command))//③
reject(command);//④
}
}
线程池的使用
1.创建一个线程池
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具(在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的)。
真正的线程池接口是ExecutorService(继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等)。
抽象类AbstractExecutorService 实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法。(没有实现execute 方法,因而是抽象类)
ThreadPoolExecutor是线程池实现的核心类(Executor接口中定义的execute方法的作用就是执行提交的任务,该方法在抽象类AbstractExecutorService中没有实现,由该类中实现),Executors中的newFixedThreadPool等方法就是由ThreadPoolExecutor实现。
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。(如果线程池中抛出异常,内部会被try,catch掉。可以通过future.get()捕获异常)
newFixedThreadPool:创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化(如果某个线程由于发生了未预期Exception而结束,那么线程池会补充一个新的线程)。
newCachedThreadPool:创建一个可缓存的线程池,如果线程池当前规模超过了处理需求,那么将回收空间线程,而当需求增加时,可以添加新线程,线程池规模不存在任何限制(不推荐使用,线程池规模不可控)大小为Integer.MAX_VALUE,2的31次方-1。
newSingleThreadExecutor:是一个单线程的Executor,如果这个线程异常结束,会创建另一个线程替代,它能保证依照任务在队列中的顺序串行执行(FIFO, LIFO, 优先级)。
newScheduledThreadPool:创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
ExecutorService threadPool = Executors.newFixedThreadPool(3);
ExecutorService threadPool = Executors.newCachedThreadPool();
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.execute(Runnable r);
threadPool.shutdown();将任务执行完毕后关闭
threadPool.shutdownNow();立即关闭,并返回没有执行的任务(List<Runnable>)
上述代码也表明:接口是不能实例化的,但是接口可以声明一个引用,指向其实现类,也就是说,在实际中返回值都是这个接口的实现类的对象
2.向线程池提交任务
1)execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功.
threadpool.execute(new Runnable()
{
public void run()
{
//TODO
}
});
2)submit方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取值,get方法会阻塞当前线程直到任务完成,而使用get(long timeout, TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回,这时候可能任务还没执行完。
Future<Object> future =executor.submit(harReturnValuetask);
try
{
Object s= future.get();
}catch(InterruptedException e)
{
}finally
{
executor.shutdown();
}
3.关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
shutdownNow:
1.首先将线程池的状态设置成STOP
2.停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
shutdown:
1.将线程池的状态设置成SHUTDOWN状态
2.中断所有没有正在执行任务的线程,不再接受新的任务。
核心线程池的内部实现
对于核心的几个线程池,无论是newFixedThreadPool()方法,newSingleThreadExecutor()还是newCachedThreadPool()方法,虽然看起来创建的线程有着完全不同的特点,但是内部实现均使用了ThreadPoolExecutor 实现都是return了一个 new ThreadPoolExecutor().
核心的线程池都是ThreadPoolExecutor类的封装.
通过ThreadPoolExecutor来创建一个线程池:
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,
milliseconds,runnableTaskQueue,handler,unit);
1)corePoolSize(线程池的基本大小)
2)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。
3)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。
4) unit keepAlivetime的单位.
5)2)runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。(ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue)
newFixedThreadPool 返回了一个corePoolSize和maximumPoolSize大小一样的,并且使用了LinkedBlockingQueue任务队列的线程池,
newSingleThreadExecutor()返回了一个单线程的线程池.
newCachedThreadPool()返回corePoolSize为0, maximumPoolSize无穷大的线程池, 并使用SynchronousQueue队列.
6)几种拒绝策略
拒绝策略是系统超负荷运行时的补充措施,同时,等待队列中也已经满了,塞不下新任务了.JDK提供四种拒绝策略
AbortPolicy 直接抛出异常,阻止系统正常工作
CallerRunsPolicy 只要线程池未关闭,该策略直接在调用者线程(调用execute()方法的线程)中运行当前被丢弃的任务,所以这种策略不会真的丢弃任务,但是任务提交线程的性能极有可能急剧下降.
DiscardOledestPolicy 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务.
DiscardPolicy 默默丢弃无法处理的任务,不予以任何处理.
7)Runnable和Callable的区别
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得,call方法有返回值,run方法没有。返回值通过Future.get()方法得到
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
合理配置线程池
性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应该配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集性的任务,线程并不是一直在执行任务,则配置尽可能多的线程 如2*Ncpu.