java线程池

一、什么是线程池

        在java中,我们可以利用线程做很多事情,创建多个线程来高效完成任务。

        线程池可以看作是线程的集合,是一种基于池化思想管理线程的工具。

举个例子:

for(int i=0;i<线程数量;i++){
    Thread thread=new Thread(任务);
    thread.start();
}

        这样的方式确实能够并发完成任务,但是也带来了一定的问题:线程数会随着任务数的增多而增多,当任务数量非常大的时候,反复的创建和销毁线程会带来极大的内存开销,同时也会降低计算机的性能。

        这时我们就需要使用线程池将线程资源集中管理。

二、线程池的任务调度

        我们已经知道线程池的作用是控制线程的数量。那他到底是怎样工作的,在处理过程中将任务放入队列,在线程创建后启动这些任务,如果线程数量超过了最大数量,超过数量的线程排队等候,等其他线程执行完毕再从队列中取出来执行。具体描述如下:

线程池的任务调度流程:

  1. 任务提交:通过调用线程池对象的execute(Runnable task)submit(Callable task)方法,将任务提交给线程池。提交的任务会被封装为RunnableCallable对象,并放入任务队列中等待执行。
  2. 任务队列:线程池使用任务队列来存储等待执行的任务。任务队列可以是不同的BlockingQueue实现类,例如ArrayBlockingQueueLinkedBlockingQueue等。任务队列充当了缓冲区的角色,保存了尚未被执行的任务
  3. 线程分配策略:线程池根据当前的工作状态和线程数量来调度任务的执行。具体的线程分配策略取决于线程池的类型和配置。
    • 核心线程数以内的任务:如果线程池中的线程数量小于核心线程数,线程池会创建新线程来执行任务,即使存在空闲线程。
    • 核心线程数以外的任务:如果线程池中的线程数量已达到核心线程数或更多,并且任务队列未满,新任务会被放入任务队列中,等待被执行。
    • 最大线程数以内的任务:如果任务队列已满 但 线程池中的线程数量小于最大线程数,线程池会创建新线程来执行任务。
    • 最大线程数以外的任务:如果线程池中的线程数量已达到最大线程数且任务队列已满,根据预定义的拒绝策略来处理新的任务提交,例如抛出异常或执行其他自定义操作。
  4. 任务执行:线程池中的线程从任务队列中获取任务并执行。当线程执行完一个任务后,会从任务队列中获取下一个任务继续执行,直到线程池关闭或没有新的任务。
  5. 线程池状态:线程池维护着一定的状态来管理任务调度的流程。
    • 运行状态(Running):线程池正在运行,可以接收任务并执行。
    • 关闭状态(Shutdown):线程池不再接收新任务,但会继续执行已提交的任务。
    • 关闭中状态(Shutting down):线程池正在关闭,正在执行的任务会继续执行,而未执行的任务将被丢弃。
    • 终止状态(Terminated):线程池已完全关闭,所有任务已执行完毕。
  6. 线程池的关闭:当不再需要线程池时,应该合理地关闭线程池,释放相关资源。
    • 调用shutdown()方法:线程池进入关闭中状态,不再接受新任务,但会执行已提交的任务,然后逐渐关闭。
    • 调用shutdownNow()方法:线程池立即关闭,尝试终止正在执行的任务,并返回未执行的任务列表。

        通过线程池的任务调度流程,可以实现任务的提交、等待执行和线程的分配,以合理地管理系统资源、提高任务执行效率和线程利用率。

线程分配策略流程图:

java线程池_第1张图片

三、线程池的好处

1、线程复用。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

4、控制了资源的总量,合理利用 CPU 和内存。由于复用线程,CPU 和 内存相较于创建多个线程来说占用更低。

四、线程池的种类及使用场景

java提供了四种线程池,分别对应不同的应用场景。Java中的线程池相关类位于java.util.concurrent包下。

考虑多线程因素:

任务数、任务执行时间、线程数、CPU核数

1、newCachedThreadPool 可缓存线程池

  • ExecutorService executor = Executors.newCachedThreadPool()
  • 概念:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • 特点:这类线程池没有核心线程,全是非核心线程。线程池中的线程数量不固定,根据任务的数量动态调整线程数量。
  • 使用场景:适用于任务量较大且任务执行时间较短的场景,可以根据任务的到达速率自动调整线程数量,提高任务的执行效率。
    • corePoolSize为0,maximumPoolSize为无限大意味着线程数量可以无限大
    • keepAliveTime为60s,意味着线程空闲时间超过60s就会被杀死
    • 它采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,意味一有请求到来,就会立刻找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

2、newFixedThreadPool  固定大小线程池

  • ExecutorService executor = Executors.newFixedThreadPool(int nThreads)
  • 概念:可以创建一个定长的线程池。定长线程池最多只能同时执行一定个数的线程,这个容量在new的时候设定。
  • 特点:线程池中的线程数量固定,不会发生变化。这类线程池里边全是核心线程,没有非核心线程,也没有超时机制,任务大小也没有限制,数量固定,即使是空闲状态,线程也不会被回收,除非线程池被关闭。
  • 使用场景:适用于需要控制并发线程数量的场景,如服务器端的任务处理、网络请求处理等,可以避免因线程过多而导致系统资源耗尽。
    • 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列,实际线程数量维持再用户设定数量,因此永远不可能拒绝任务。

3、newScheduledThreadPool  计划线程池

  • ScheduledExecutorService executor = Executors.newScheduledThreadPool(int corePoolSize)
  • 概念:创建一个定时线程池,支持延迟执行和周期性任务执行。后一种执行方式类似于单片机的定时器中断。
  • 特点:线程池中的线程可用于定时执行任务或周期性执行任务。这类线程池核心线程数量是固定的,但是非核心线程是没有限制的,并且非核心线程一闲置就会被回收。
  • 使用场景:适用于需要定时延迟执行任务的场景,如定时任务调度、定时数据备份等。
    • 它采用DelayQueue存储等待的任务,是一个无界队列,内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber序列号排序;
    • 任务线程会从DelayQueue取已经到期的任务去执行,执行结束后重新设置任务的到期时间,再次放回DelayQueue。

4、newSingleThreadExecutor  单线程线程池

  • ExecutorService executor = Executors.newSingleThreadExecutor()
  • 概念:创建一个单线程化的线程池,这个线程池当前池中的线程死后(或发生异常时),才能重新启动新的一个线程来替代原来的线程继续执行下去。也就是说按照单线程的模式,会按照线程添加的顺序,一个一个的执行这些线程的工作。
  • 特点:线程池中只有一个核心线程工作,确保所有任务按顺序执行。
  • 使用场景:适用于需要顺序执行任务的场景,如事件处理、日志记录等,可以避免多线程带来的并发问题。

你可能感兴趣的:(java,开发语言)