Java线程池

引自:https://www.bilibili.com/video/BV1e5411N7Jm?p=1

一、使用多线程的好处

​ 1.降低资源的消耗。通过重复利用已经创建好的线程,降低线程创建和销毁对性能的影响。
​ 2.提高响应速度。当任务到达时可以不用等待线程创建就能立即执行。
​ 3.方便对线程的管理。线程的创建和销毁由线程池统一管理,不会过多的消耗系统的资源。

二、线程池的参数

public ThreadPoolExecutor(int corePoolSize,核心线程数
                          int maximumPoolSize, 非核心线程数
                          long keepAliveTime,时间
                          TimeUnit unit,时间单位
                          BlockingQueue<Runnable> workQueue,队列
                          ThreadFactory threadFactory,线程工厂
                          RejectedExecutionHandler handler拒绝策略) {
}

​ 1.核心线程池的个数
​ 2.线程池的最大线程数
​ 3.线程的存货时间
​ 4.任务队列。保存那些等待执行的任务
​ 5.线程工厂。用于创建新的线程
​ 6.线程的饱和策略。当线程池和队列都满了,再加入进来的任务会执行哪种策略。

1.corePoolSize -> 该线程池中核心线程数最大值

核心线程:在创建完线程池之后,核心线程先不创建,在接到任务之后创建核心线程。并且会一直存在于线程池中(即使这个线程啥都不干),有任务要执行时,如果核心线程没有被占用,会优先用核心线程执行任务。数量一般情况下设置为CPU核数的二倍即可。

2.maximumPoolSize -> 该线程池中线程总数最大值

线程总数=核心线程数+非核心线程数

非核心线程:简单理解,即核心线程都被占用,但还有任务要做,就创建非核心线程

3.keepAliveTime -> 非核心线程闲置超时时长

这个参数可以理解为,任务少,但池中线程多,非核心线程不能白养着,超过这个时间不工作的就会被干掉,但是核心线程会保留。

4.TimeUnit -> keepAliveTime****的单位

TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天

5.BlockingQueue workQueue -> 线程池中的任务队列

默认情况下,任务进来之后先分配给核心线程执行,核心线程如果都被占用,并不会立刻开启非核心线程执行任务,而是将任务插入任务队列等待执行,核心线程会从任务队列取任务来执行,任务队列可以设置最大值,一旦插入的任务足够多,达到最大值,才会创建非核心线程执行任务。

workQueue****有四种:

1.SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

2.LinkedBlockingQueue:这个队列接收到任务的时候,如果当前已经创建的核心线程数小于线程池的核心线程数上限,则新建线程(核心线程)处理任务;如果当前已经创建的核心线程数等于核心线程数上限,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

3.ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误,或是执行实现定义好的饱和策略

4.DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

6.ThreadFactory threadFactory -> 创建线程的工厂

可以用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。

7.RejectedExecutionHandler handler -> 饱和策略

这是当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy, 表示无法处理新任务,并抛出 RejectedExecutionException 异常。此外还有3种策略,它们分别如下。
(1)CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
(2)DiscardPolicy:不能执行的任务,并将该任务删除。
(3)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。

三、新线程进入线程池流程

​ 当一个任务进来,判断线程池当前数量是否大于核心线程池数量。如果小于,就创建一个新的线程来执行任务。
​ 如果大于,就判断任务队列是否满了。没满的话,把任务添加到工作队列中。
​ 如果满了就判断当前线程池中线程数量是否大于最大线程数量。如果小于,则创建一个新的线程来执行任务。
​ 如果大于,则执行饱和策略(比如直接拒绝)。

四、线程池的种类

​ 1.newCachedThreadPool。可以无限扩大的线程池,适用于任务量比较大,执行时间比较短,一般在60秒以内的情况,不会造成CPU的过度切换。
​ 2.newFixedThreadPool。创建一个固定大小的线程池,适用于任务数量固定并且执行时间较长的情况。
​ 3.newSingleThreadPool。创建一个单线程的线程池,适用于需要保证顺序执行任务。
​ 4.newScheduledThreadPool。适用于执行延迟或者周期性的任务。

4.1 CachedThreadPool

CachedThreadPool是一个根据需要创建线程的线程池。

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

CachedThreadPool的corePoolSize是0,maximumPoolSize是Int的最大值,也就是说CachedThreadPool没有核心线程,全部都是非核心线程,并且没有上限。keepAliveTime是60秒,就是说空闲线程等待新任务60秒,超时则销毁。此处用到的队列是阻塞队列SynchronousQueue,这个队列没有缓冲区,所以其中最多只能存在一个元素,有新的任务则阻塞等待。

4.2 FixedThreadPool

可重用固定线程数的线程池,超出的线程会在队列中等待,在Executors类中我们可以找到创建方式:

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

FixedThreadPool的corePoolSize和maximumPoolSize都设置为参数nThreads,也就是只有固定数量的核心线程,不存在非核心线程。keepAliveTime为0L表示多余的线程立刻终止,因为不会产生多余的线程,所以这个参数是无效的。FixedThreadPool的任务队列采用的是LinkedBlockingQueue。

4.3 SingleThreadExecutor

SingleThreadExecutor是使用单个线程工作的线程池。其创建源码如下:

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

可以看到总线程数和核心线程数都是1,所以就只有一个核心线程。该线程池采用链表阻塞队列LinkedBlockingQueue,先进先出原则,所以保证了任务的按顺序逐一进行。

4.4 ScheduledThreadPool

ScheduledThreadPool是一个能实现定时和周期性任务的线程池,它的创建源码如下:

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

这里创建了ScheduledThreadPoolExecutor,继承自ThreadPoolExecutor,主要用于定时延时或者定期处理任务。

corePoolSize是传进来的固定值,maximumPoolSize无限大,因为采用的队列DelayedWorkQueue是无解的,所以maximumPoolSize参数无效。

当执行scheduleAtFixedRate或者scheduleWithFixedDelay方法时,会向DelayedWorkQueue添加一个实现RunnableScheduledFuture接口的ScheduledFutureTask(任务的包装类),并会检查运行的线程是否达到corePoolSize。如果没有则新建线程并启动ScheduledFutureTask,然后去执行任务。如果运行的线程达到了corePoolSize时,则将任务添加到DelayedWorkQueue中。DelayedWorkQueue会将任务进行排序,先要执行的任务会放在队列的前面。在跟此前介绍的线程池不同的是,当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中。

五、execute方法

六、新任务提交后线程池的处理流程

  • 提交优先级
  • 执行优先级
原文地址:https://www.cnblogs.com/smalldong/p/14461395.html