一、进程池
进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。
当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:
1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。
2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。
当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。
二、线程池
1.使用线程池的场景
Java线程池是使用最多的并发框架,几乎所有的异步或并发执行任务都可以使用线程池。
2.使用线程池的好处
①降低资源消耗
通过重复利用已经创建的线程来降低线程创建和销毁所造成的消耗
②提高响应速度
当任务到达时,任务不需要等待线程创建就可以立即执行
③提高线程的可管理性
因为线程是稀缺资源,如果无限制的创建线程,不仅会消耗资源,也会降低系统的稳定性,使用线程池可以对形成进行统一分配、调优和监控。
3.线程池的实现原理
①当提交一个新的任务去线程池的时候,首先线程池会判断核心线程池(corePool)中的线程是否都在执行任务。如果不是,则创建一个新的线程来执行任务,否则,进入下一个流程。(需要获取全局锁)
②线程池会判断工作队列(堵塞队列 BlockingQueue)是否已满,如果堵塞队列已满,则该任务加入到这个堵塞队列中,如果满了,则进入下一个流程。
③线程池判断线程池中的所有线程(maximumPool)是否都处于工作状态,如果没有,则创建一个新的工作线程来完成任务(此操作也需要获取全局锁),如果所有线程都处于工作状态,则交给饱和策略来处理这个任务,默认是抛出异常(AbortPolicy策略,除此之外还有三个策略:
CallerRunsPolicy:只用调用者所在的线程来执行任务
DiscardOldestPolicy:丢弃队列里最近一个任务,并执行当前任务
DiscardPolicy:丢弃该任务,不处理。
)。
注意:线程池的设计其实主要是在执行execute方法的时候,尽可能的去避免获取全局锁(因为那是一个很严重的可伸缩瓶颈)。在线程池完成预热后,当前运行的线程是大于核心线程池的,所以几乎所有的execute方法都会把该任务加入到阻塞队列中去,从而避免获取全局锁。
工作线程:线程池在创建线程的时候会把线程封装成工作线程Worker,Worker在执行完任务之后,还会循环获取工作队列中的任务来执行。
所以线程池中的线程执行任务分为两种情况:
<1>在execute()方法中创建一个线程的时候,会让这个线程执行当前任务
<2>当这个线程执行完任务的时候回反复从阻塞队列中获取任务来执行。
4.线程池的使用
①线程池的创建
//线程池的创建可以使用new 一个对象来完成
//简介各个参数的意思
/*
* corePoolSize:线程池的基本大小,这个参数,当提交任务时如果当需要执行的任务数小于基本线程数的时候
* 就会创建线程,即使有空余线程的时候也创建线程,直至需要任务数大于基本线程池的数量的时候就不再创建
* maximumPoolSize:线程池的最大线程数量,当阻塞队列满的时候,会创建新的线程。
* workQueue:任务队列,用于保存等待执行任务的阻塞队列,可以选择:
* 基于数组有界的阻塞队列、基于链表的阻塞队列(静态工厂方法使用了此队列)、不存储元素的阻塞队列、一个具有优先级的无线阻塞队列
* threadFactory:用于创建线程的工厂,可以给线程设置具有意义的名字
* keepAliveTime:一种自定义饱和策略的实现方式,其实应该是RejectedExecutionHandler,基本策略有四种,上文已介绍
*/
ThreadPoolExecutor tpe = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
②向线程池提交任务
execute和submit两个方法,其中前者无返回值,后者会返回一个future类型的对象,通过future对象可以判断该任务是否执行成功,并且可以使用future这个对象的get方法来获取返回值,该方法会阻塞到当前线程直至任务完成
③关闭线程池
可以使用shutdown和shutdownNow方法来关闭线程池,他们的原理是遍历线程池中的所有的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能蝾螈无法停止。他们两者的区别是:后者会把线程池的状态设置为STOP,然后尝试停止所有的正在执行或暂停的线程,并返回等待执行任务的列表,而前者之后把线程池的状态设置为SHUTDOWN状态,然后中断所右没有执行任务的线程,可以用isShutdown方法来检测线程池是否终止完成。
④合理的配置线程池
使用线程池的时候要根据性质不同的任务选取不同规模的线程池进行处理
<1>CPU密集的任务应该配置更小的线程
<2>IO密集的,配置更多的线程
<3>混合型的可以拆分成不同任务来完成
<4>优先级不同的的任务可以使用优先级队列的无界阻塞队列来处理
<5>执行时间不同的交给不同规模的的线程池来处理,也可以使用优先级队列
<6>依赖数据库连接池的任务,因为线程需要提交SQL等待数据库返回结果,所以等待的时间就会很长,此时,应该设置多多的线程数的线程池
<7>建议使用有界队列。可以根据需要把阻塞队列设置大一些,因为若是选择误解队列但是如果后台任务池里面的任务插入数据库的时候,数据库出现问题的时候,那么任务就会在阻塞队列中无限堆积,可能会造成内存无限堆积,系统崩掉。