线程池的作用:控制线程的数量,处理过程中把任务放到队列中,然后在线程创建后启动这些任务。如果线程数量超过了最大数量,超出的线程等待。等到其他线程执行完毕后,再从队列中取出任务来执行。
线程池的作用:
- 线程复用
- 管理线程
- 控制最大并发
有Java为我们提供了这样三种直接创建线程池的方法,但不推荐使用。因为如果使用这些方式,在出现问题的时候,不容易快速定位到底是哪个线程出的问题。而且它们的任务队列都是LinkedBlockingQueue,这是由链表结构组成的有界(但默认值大小为Integer.MAX_VALUE)阻塞队列。
ExecutorService pool1 = Executors.newSingleThreadExecutor();//一池一线程
ExecutorService pool2 = Executors.newFixedThreadPool(5);//一池五线程
ExecutorService pool3 = Executors.newCachedThreadPool();//一池N线程
顺便提一下阻塞队列吧
当阻塞队列是空的时候,从队列中获取元素的操作会被阻塞;
当阻塞队列是满的时候,往队列中添加元素的操作会被阻塞。
阻塞队列的种类
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界(但默认值大小为Integer.MAX_VALUE)阻塞队列
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayBlockingQueue:使用优先级队列实现的延迟无界阻塞队列
- SynchornousQueue:不存储元素的阻塞队列,也即单个元素的队列(即时消费)
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDueue:由链表结构组成的双向阻塞队列
线程池的七大参数:
- corePoolSize:线程池中常驻的核心线程数
- maxiumPoolSize:能够容纳的最大线程数,必须大于等于1。
- keepAliveTime:多余空闲线程的存活时间。当前线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程位置。
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但未被执行的任务
- threadFactory:生成线程池中工作线程的线程工厂,用于创建线程,默认即可。
- handler:拒绝策略,表示当队列满了并且工作线程大于等于最大线程数时,如何拒绝请求。
线程池的工作原理:
- 在创建好线程池后,等待提交过来的任务请求
- 当调用execute方法添加一个请求任务时,线程池就会做以下判断
2.1 如果当前运行的线程数量少于corePoolSize,那么马上创建线程运行这个任务
2.2 如果当前运行的线程数量大于等于corePoolSize,那么将这个任务放进任务队列
2.3 如果当前任务队列已经满了但是当前运行的线程数量少于maxiumPoolSize,那么就要立即创建非核心线程运行这个任务
2.4 如果当前任务队列满了并且正在运行的线程数量大于等于maxiumPoolSize,那么线程池就会启动饱和拒绝策略 - 当一个线程完成任务后,它会从队列中取下一个任务来执行
- 当一个线程无事可做超过一定时间(keepAliveTime),线程池就会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。所以线程池所有任务完成之后,最终会收缩到corePoolSize的大小。
拒绝策略:
当达到最大线程数且等待队列满时,就要启用拒绝策略。
- AbortPolicy(默认):直接抛出RejectedException。
- CallerRunsPolicy:不会抛弃任务也不会抛出异常,而是回退给调用者。
- DiscardOldestPolicy:丢弃等待最久的任务。
- DiscardPolicy:直接丢弃任务,不做任何处理。
如何合理配置线程数
看是CPU密集型任务还是IO密集型任务
System.out.println(Runtime.getRuntime().availableProcessors());//查看核心数
CPU密集型:CPU核心数 + 1
IO密集型:
- CPU核心数 * 2 (IO密集型不是一直在执行任务,所以应该多配置些线程)
- CPU核心数/(1 - 阻塞系数) (阻塞系数一般为0.8~0.9)
网友评论