谈谈线程池的理解
线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。
线程池的状态分为:RUNNING , SHUTDOWN , STOP , TIDYING , TERMINATED
RUNNING:运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
。该状态的线程池会接收新任务,并处理工作队列中的任务。
调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
调用线程池的shutdownNow()方法,可以切换到STOP停止状态;
SHUTDOWN :关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入TIDYING状态;
STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;
线程池中执行的任务为空,进入TIDYING状态;
TIDYING :整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;terminated()执行完毕,进入TERMINATED状态;
TERMINATED : 该状态表示线程池彻底终止。
线程池的核心参数
线程池主要有7个核心参数,分别是:CorePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(非核心线程数存活时间)、TimeUnit(存活时间单位)、BlockingQueue(工作队列)、ThreadFactory(线程工厂)、RejectedExecutionHandler(拒绝策略)。
核心线程数是指线程池中最小线程数,核心线程数不会被回收,超过核心线程数的线程在大于存活时间时会被回收;
最大线程数是指线程池中最多可以创建的线程数,包括核心线程数和非核心线程数;
工作队列主要是阻塞工作队列,用于存储等待执行的任务。阻塞队列在生产者-消费者模型中有明显的表现:一个线程用于存储(生产者),一个线程用于取出(消费者)。当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列,当队列中数据存满内存空间时,生产者端的所有线程会被自动阻塞,在保证并发安全的同时,提高了队列的存取效率。常见的阻塞工作队列有:ArrayBlockingQueue(基于数组的有界队列)、LinkedBlockingQueue(基于链表的无界队列)、DelayedWorkQueue(基于堆结构的演延时队列)、PriorityBlockingQueue(基于优先级的无界队列)、SynchronousQueue(同步队列)等;
线程工厂主要用于线程池创建线程,默认的工厂是DefaultThreadFactory;
拒绝策略是指当线程池线程内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理。
线程池运行过程
线程池中提交一个任务时,会从线程池中分配一个空闲线程;
如果不存在空闲线程,会判断当前核心线程数是否已满,如果未满,添加线程任务至工作队列中,等待线程池分配任务;
如果已满,判断当前线程数是否大于最大线程数,如果小于最大线程数,创建非核心线程执行任务,如果大于最大线程数,采用拒绝策略处理。
常见线程池有哪些以及使用场景?
FixedThreadPool(固定线程数的线程池): 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
CachedThreadPool(根据线程数动态调整的线程池):用于并发执行大量短期的小任务。
SingleThreadExecutor(单线程线程池):适用于串行执行任务的场景,将任务按顺序执行。
ScheduledThreadPool(能实现定时、周期性任务的线程池):周期性执行任务,并且需要限制线程数量的需求场景。
为什么要使用线程池?
在java中使用多线程执行任务,由于每个线程都要进行创建和销毁,导致性能和内存上的开销都非常大,而导致资源不足,所以使用线程池,每次创建时都从线程池中获取,等线程任务执行结束,又回收到线程池中,减少创建和销毁的次数,能一定程度上提高效率,并且可以有效地对线程进行统一的管理监控。