线程池的好处?
在开发过程中,合理地使用线程池,能够带来3个好处:
a:降低资源消耗 通过重复利用已经存在的线程降低线程创建和销毁线程造成的消耗
b:提高响应速度 当任务到达时,任务可以不需要等待线程创建就可以立即执行
c:提高线程的可管理性 线程是稀缺资源,如果无限制地创建,不仅会消耗系统系统资源,还会降低系统的稳定性
线程池的工作原理?
1)判断核心线程池是否已满,如果没满会创建新的工作线程来执行任务,满的话则会进入下一个步骤
2)判断工作队列是否已满,如果没满的话则会将该任务存储在工作队列中等待执行,满的话则进入下一个步骤
3)判断线程池是否已满,没满的话则会创建新的工作线程执行任务,满的话交给饱和策略处理该任务
线程池有哪些?
1)newCachedThreadPool 可缓存的线程池
2)newFixedThreadPool 定长线程池
3)newScheduleThreadPool 定长线程池
4)newSingleThreadPool 单线程化的线程池
线程池的几个重要参数
核心线程数量 线程最大线程数 工作队列 线程工厂 饱和(拒绝)策略
ThreadPoolExecutor执行execute分下面4种情况?
1)如果当前运行的线程少于corePoolSize,则会创建新的线程来执行任务(执行这一步需要获取全局锁)
2)如果运行的线程大于等于corePoolSize,则将任务加入BlockingQueue
3)如果无法将任务加入BlockingQueue(队列已满),则会创建新的线程处理任务(执行这一步需要获取全局锁)
4)如果创建新线程使得当前运行的线程超出maxmumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时尽可能地避免获取全局锁(获取全局锁是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热后(当前运行的线程大于corePoolSize),几乎所有的execute()方法都是执行的步骤2,步骤2不需要全局锁
线程池的线程执行任务分两种情况:
1.在execute()方法中创建一个线程时,会让这个线程执行该任务
2.反复从BlockingQueue获取任务来执行
runnableTaskQueue?
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列
ArrayBlockingQueue:是一个基于数组的有界阻塞队列,该队列按FIFO(先进先出)原则对元素进行排序
LinkedBlockingQueue:一个基于链表的无界阻塞队列,按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用这个队列
SynchronousQueue:一个不存储元素的阻塞队列。每个插入必须等待另一个线程调用移除操作,否则插入一直处于阻塞状态,吞吐量高于LinkedBlockingQueue,静态方法Executors.newCachedThreadPool使用这个队列(缓存)
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
RejectedExecutionHandler?
当队列和线程池都满了,说明线程池状态达到饱和,必须采取一种策略处理提交的新任务。默认情况下是AbortPolicy
策略(还可以自定义拒绝策略):
AbortPolicy:直接抛出异常
CallerRunsPolicy:只用调用者所在线程来运行任务
DiscardOldPolicy:丢弃队列里最近的一个任务,并执行当前任务
DIscardPolicy:不处理,丢弃掉
向线程池提交任务?
有两个方法可以向线程池提交任务:execute()和submit()
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被执行成功
submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个对象可以判断是否执行成功,并且可以通过future.get()来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完,但能够保存不会永久阻塞
关闭线程池?
可以通过调用线程池的shutdown()或shutdownNow()方法来关闭线程池。原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt()方法来中断线程,所以无法响应中断是线程可能永远无法终止。两个关闭方法有区别,shutdownNow首先将线程池的状态设置为stop,然后尝试停止所有正在执行或者暂停的线程,并返回等待执行的任务列表。而shutdown只是将线程池的状态设置为shutdown状态,然后中断所有没有正在执行的任务的线程。
只要调用了这两个关闭方法的任意一个,isShutdown方法就会返回true,当所有的任务都关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true,至于用哪一种方法关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown()方法,如果任务不一定要执行完,则可能调用shutdownNow()方法
如何合理配置线程池的大小?(n个cpu)
如果是cpu密集型任务,需要尽量压榨cpu,参考值设为n+1
如果是io密集型任务,参考值设为2*n
AQS?
锁在实现时都使用了一个共同的基类,即AbstractQueuedSynchronizer,AQS是一个用于构建锁和同步器的框架,在基于AQS构建的同步器中,只可能在一个时刻发生阻塞,从而降低上下文切换的开销,并提高吞吐量,基于AQS的同步器类中,最基本的操作是获取和释放操作
AQS实现公平锁和非公平锁?
公平锁:首先调用AQS的acquire方法,然后去回调tryAcquire方法,tryAcquire是这样实现的:
1.判断state是否为0,为0则说明可以获取
2.然后判断AQS队列中是否有等待的线程,没有的话则用CAS尝试获取,获取成功后将锁的持有线程修改为当前线程
3.假如是持有该锁的线程想再次获取锁,那么就会对state加1
4.假如队列中有等待的线程,那么会将该线程也放入等待队列中
非公平锁:不需要判断AQS队列中是否有等待的线程,直接去发起获取锁的操作
1.判断state是否为0,为0则开始尝试通过CAS操作获取锁,不为0则直接返回false
2.假如是持有该锁的线程想再次持有锁,那么state+1
区别:非公平锁不需要判断AQS队列中是否有等待线程,是在获取锁失败的情况下再进入AQS队列中的,而公平锁假如AQS队列中有等待线程的话就会直接进入队列中
jdk自带线程池的缺陷?
1.非核心线程的创建是在核心线程已满并且阻塞队列也满了的情况下才创建的,这个时候假如拒绝策略是抛弃任务的话,那么当大量的任务到达时可能就会都被抛弃掉
2.当线程池核心线程数量满了,阻塞队列也满了,这个时候新加的任务被非核心线程执行,这样的话先提交的任务反而后执行了,假如任务之间有依赖关系,那么就会降低线程的调度效率
优化:1.自定义一个等待队列,当队列中的任务达到一定数量时就去创建非核心线程而不是满了才创建。可以使用LinkedBlockingQueue,产生一个双生产者,单消费者的模型,其中生产者分为普通生产者和超限生产者
2. 采用代理类,将任务绑定时间延迟到任务执行时而不是添加的时候,增加新的任务队列,按添加顺序保证真正的执行任务,运行时动态地从新增任务中获取头部任务