线程池的基本介绍

378 阅读5分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

为了减少我们创建以及销毁线程对内存的消耗,采用线程池来使线程得到复用。最常见的线程池类型有以下几种:

  • newFixedThreadPool:固定数量的线程池。

  • newSingleThreadExecutor:单线程池。

  • newCachedThreadPool:可缓存线程池。

  • newScheduledThreadPool:大小无限制的线程池,可以定时和周期性的执行线程。

  • newWorkStealingPool:java8新增的线程池,它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行。

简单使用

newFixedThreadPool

固定数量的线程池,我们这里的参数是3,也就是说有三个线程可以执行任务,每提交一个任务就是一个线程,当任务数达到线程数量,新的任务就进入等待队列。

我们有五个任务,所以当三个线程执行三个任务的时候,剩下的两个任务进入等待,等其中一个线程执行完毕,再执行剩下的任务。

package com.wangscaler.thread;
​
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
​
/**
 * @author WangScaler
 * @date 2021/8/23 14:00
 */public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        try {
            for (int i = 0; i < 5; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName())
                );
​
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

执行结果如下:

pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1

适合用于执行长期的任务。

newSingleThreadExecutor

单一线程的线程池,所以从始至终都只有一个线程执行任务。

将上述的代码ExecutorService pool = Executors.newFixedThreadPool(3); 替换成ExecutorService pool = Executors.newSingleThreadExecutor();执行结果如下:

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

从始至终都只是线程1在执行任务,从而证实了这是个单线程的线程池。适合用于一个任务一个任务执行的场景。

newCachedThreadPool

当线程池的数量大于任务数的时候,就回收部分空闲线程(60s无执行的线程),当任务数增加的时候,再创建新的线程执行任务。

将上述的代码Executors.newFixedThreadPool(3);替换成Executors.newCachedThreadPool();

执行结果如下:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5

新建了五个线程执行任务,那么如果我们给上述的线程任务加上休眠之后,会创建几个线程执行任务呢?

package com.wangscaler.thread;
​
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
​
​
/**
 * @author WangScaler
 * @date 2021/8/23 14:00
 */public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 5; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName())
                );
                TimeUnit.SECONDS.sleep(1);
​
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

再次执行上述的代码,发现和上次的结果不一样了。

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

这就是可缓存线程池,根据任务数动态的管理线程数。适合用于执行很多短期异步的小程序或者负载较轻的服务器。

上述的方式只是演示使用,真正开发时不建议这样创建线程池,到底如何创建?将在下篇文章来讲解。

七大参数

  • 1、corePoolSize:核心线程数,当任务达到核心线程数之后,将进入任务队列workQueue进行等待。

  • 2、maximumPoolSize:最大线程数即线程池能够容纳同时执行的最大线程数,注意:此值必须大于等于1

  • 3、keepAliveTime:多余的空闲线程存活时间。当前线程池数量超过corePoolSize时,当空闲时间到达keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。

  • 4、unit:keepAliveTime的时间单位。

  • 5、workQueue:任务队列,被提交但尚未执行的任务。

  • 6、threadFactory:表示生成线程池中的工作线程的线程工厂,用于创建线程,一般为默认线程工厂即可。

  • 7、handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝来请求的Runnable的策略。

执行过程

  • 1、创建完线程池之后,等待任务。

  • 2、当调用execute()。

    • 如果正在运行的线程数小于corePoolSize核心线程数,则创建线程执行任务

    • 如果正在运行的线程数不小于corePoolSize核心线程数,且workQueue任务队列未满,则将任务加入workQueue任务队列。

    • 如果任务队列也满了,且此时正在运行的线程数小于maximumPoolSize最大线程数,则立即创建新的线程执行这个任务。(注意:这里执行的任务队列以后的新任务,而不是将任务队列的任务执行和新任务加入任务队列)。

    • 如果此时正在运行的线程数也不小于maximumPoolSize最大线程数了,则线程池将启用handler饱和拒绝策略。

  • 3、当线程执行完任务,则将从workQueue任务队列获取新任务继续执行。

  • 4、如果线程处于空闲期(即无任务执行)

    • 此时线程池的线程数大于corePoolSize核心线程数,则将超过keepAliveTime空闲存活时间的线程注销回收。

    • 此时线程池的线程数不大于核心线程数,则不处理。

整个流程图大致如下:非专业作图,勿喷。

image-20210824153731069.png

今天先写到这里,明天从源码的角度看看线程的执行过程。

来都来了,点个赞再走呗!

关注WangScaler,祝你升职、加薪、不提桶!