JAVA线程池

66 阅读8分钟

juejin.cn/post/684490…

定义

管理线程的池子。以线程重复利用,避免重复创造&销毁线程达到提高资源利用率的目的

好处

1. 避免重复新建&销毁线程带来的资源浪费(因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的)

2. 提高响应速度,未使用线程池时,来一个请求创建一个线程,比较耗时,而使用线程池后,线程拿来即用

3. 重复利用线程(线程用完,再放回池子,可以达到重复利用的效果,节省资源)

几个参数

**核心线程数:**线程池中的常驻核心线程数

**最大线程数:**线程池能容下的最大线程数

**非核心线程数的存活时间:**非核心线程数 = 最大线程数 - 核心线程数,当线程的空闲时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止

**任务等待队列:**被提交但还未被执行的任务(底层如何实现)

  • ArrayBlockingQueue:基于数组实现的有界阻塞队列,FIFO
  • LinkedBlockingQueue:基于链表实现的无界阻塞队列,也可以设置容量,FIFO。在有界的情况选哪个??
  • PriorityBlockingQueue:具有优先级的无界阻塞队列。底层如何实现?
  • SynchronousQueue(同步队列)是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

**拒绝策略:**当线程池中的线程无法再处理更多的请求时,采取的拒绝策略

  • AbortPolicy:丢弃任务并抛出,抛出异常,默认方式

  • DiscardPolicy:丢弃任务,不抛异常

  • CallerRunsPolicy:接受任务,由调用线程池的线程执行

  • DiscardOldestPolicy:丢弃队列里最老的任务,将当前这个任务继续提交给线程池

为什么任务队列中的任务是runable的? 那不是一个任务就是一个线程了?

线程池工作流程

提交任务,线程池当前线程数<核心线程数,创建新线程,执行该任务

线程池当前线程数=核心线程数,等待队列未满,将该任务放入等待队列

等待队列也满了,线程池当前线程数<最大线程,创建新线程,执行该任务

线程池当前线程数=最大线程数,执行拒绝策略

JDK中内置的线程池

allowCoreThreadTimeOut 控制核心线程是否空闲超时销毁,默认为false

java中的几种线程池

ThreadPoolExecutor(coreSize,maxSize,keepAliveTime,timeUnit,threadFactory,rejectHandler)

CachedThreadPool:可缓存线程的线程池。(0,max,keepAliveTime,timeUnit,threadFactory,rejectHandler)

FixedThreadPool:固定线程数量的线程池(n,n,0,,,,)

SingleThreadPool:单线程的线程池(1,1,)

ScheduledThreadPool:定时及周期执行的线程池

FixedThreadPool

  public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

特点

线程数量固定(核心线程数=最大线程数)

keepAliveTime=0,不存在空闲销毁的线程

阻塞队列使用LinkedBlockingQueue无界阻塞队列

工作机制

  • 提交任务
  • 如果线程数少于核心线程,创建核心线程执行任务
  • 如果线程数等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列
  • 如果线程执行完任务,去阻塞队列取任务,继续执行。

**使用无界队列的线程池会导致内存飙升,**newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长(比如,上面demo设置了10秒),会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM

使用场景

FixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

尽可能少的线程,避免线程上下文切换带来的开销,保证CPU被长期、充分利用

尽可能多的任务,保证CPU一直不会处于空闲状态

newCachedThreadPool

   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

特点

核心线程数=0,最大线程数=Integer.MAX_VALUE,线程数量可以无限增大

线程空闲60s,自动销毁

等待队列使用SynchronousQueue

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

工作机制(??不理解)

  • 提交任务

  • 因为没有核心线程,所以任务直接加到SynchronousQueue队列。

  • 判断是否有空闲线程,如果有,就去取出任务执行。

  • 如果没有空闲线程,就新建一个线程执行。

  • 执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续活下去;否则,被销毁。

??

为什么要用SynchronousQueue,不用ArrayBlockingQueue

为什么需要等待队列,而不是来一个请求创建一个线程

保证队列长度为1的原因,为保证任务尽快被执行。如果队列长度>1,任务就会进入等待,而不是立马创建新线程执行该任务

使用场景

用于并发执行大量短期的小任务。原因

  • 来了任务直接执行,不会进入等待队列进行长时间等待,适用高并发。

  • 由于线程数可以无限大,如果任务太大执行时间太长会造成线程的大量创建耗尽资源

newSingleThreadExecutor

  public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

特点

核心线程数=最大线程数=1,只存在一个线程

空闲销毁时间=0,即线程一直存在,不会空闲销毁

使用LinkedBlockingQueue等待队列

工作机制

  • 提交任务
  • 线程池是否有一条线程在,如果没有,新建线程执行任务
  • 如果有,讲任务加到阻塞队列
  • 当前的唯一线程,从队列取任务,执行完一个,再继续取,一个人(一条线程)夜以继日地干活。

?? 为什么不是通用的,先判断核心队列?

适用场景

需要串行执行的任务,任务依次执行

newScheduledThreadPool(?? 不理解)

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

特点

  • 最大线程数=Integer.MAX_VALUE,线程可以无限创建
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0(??,线程常驻?)
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

工作机制

  • 添加一个任务
  • 线程池中的线程从 DelayQueue 中取任务
  • 线程从 DelayQueue 中获取 time 大于等于当前时间的task
  • 执行完后修改这个 task 的 time 为下次被执行的时间
  • 这个 task 放回DelayQueue队列中

使用场景

周期性执行任务的场景,需要限制线程数量的场景

线程池的几种状态

RUNNING

  • 该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;
  • 调用线程池的shutdownNow()方法,可以切换到STOP状态;

SHUTDOWN

  • 该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态;

STOP

  • 该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • 线程池中执行的任务为空,进入TIDYING状态;

TIDYING

  • 该状态表明所有的任务已经运行终止,记录的任务数量为0。
  • terminated()执行完毕,进入TERMINATED状态

TERMINATED

  • 该状态表示线程池彻底终止

线程池各个状态切换图

InterruptedException?

如何获取线程池执行的异常

(如何获取线程执行的异常)

在使用线程池处理任务的时候,任务代码可能抛出RuntimeException,抛出异常后,线程池可能捕获它,也可能创建一个新的线程来代替异常的线程,我们可能无法感知任务出现了异常,因此我们需要考虑线程池异常情况。

  • 在任务代码中通过try/catch捕获异常

  • 通过submit执行的任务,可以通过Future对象的get方法接收抛出的异常

  • 为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常

  • 重写ThreadPoolExecutor的afterExecute方法,处理传递的异常引用

线程池在工作中的应用

jdk8加入的workStealingPool

有哪些线程池框架

线程池中的线程用完了怎么办? 会阻塞吗

为什么会区分核心和非核心线程

ArrayBlockingQueue与****LinkedBlockingQueue 区别

**DelayQueue?
**

****SynchronousQueue 场景


******1。线程池有哪些参数 2。JDK中有哪些内置的线程池 3。这些线程池使用的时候有哪些注意点


线程池源码

线程池是一开始就初始化一定的线程还是来一个创建一个

io密集和cpu密集系统距离

不同任务需要用不同的抛弃策略,怎么办

主流程线程与非主流程线程?

Alibaba命名规范的解释:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明: Executors 返回的线程池对象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。 2) CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

自定义线程

池化技术

类似字符串常量池,数据库连接池,HttpClient连接池等,都是用的池化技术。