线程池原理

线程池原理

概念

  1. 线程池管理器: 用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
  2. 工作线程: 线程池中的线程,在没有任务时处于等待状态,可以循环的执行任务;
  3. 任务接口: 每个任务必须实现的接口,以供工作线程调度任务的执行,他规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4. 任务队列: 用于存放没有处理的任务。提供一种缓冲机制。
    在这里插入图片描述

线程池API–接口定义和实现类

类型名称描述
接口Executor最上层的接口,定义了执行任务的方法execute
接口ExecutorService继承了Executor接口,扩展了Callable、Future、关闭方法。
接口ScheduledExecutorService继承了ExecutorService,增加了定时任务相关的方法
实现类ThreadPoolExecutor基础、标准的线程池实现
实现类ScheduledThreadPoolExecutor继承了 ThreadPoolExecutor,实现了ScheduledExecutorservice中相关定时任务的方法

线程池API–Executors工具类

你可以自己实例化线程池,也可以用Executors 创建线程池的工厂类,常用方法如下:
new FixedThreadPool(int nThreads): 创建一个固定大小、任务队列容量无界的线程池。核心线程数=最大线程数。
newCachedThreadPool(): 创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毀释放。线程数随任务的多少变化。适用于执行耗时较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_ VALUE
new SingleThreadExecutor(): 只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按加入的顺序一个个依次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool(1)的区别在于,单一线程池的池大小在newSingleThreadExecutor方法中硬编码,不能再改变的。
newScheduledThreadPool(int corePoolSize) : 能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数= Integer.MAX_VALUE。

线程池原理–任务execute过程

在这里插入图片描述

  1. 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
  2. 工作队列是否已满?没满,则将新提交的任务存储到工作队列里。
  3. 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
  4. 最后,执行拒绝策略来处理这个任务。
代码示例
ThreadPoolExecutor

测试代码

/**
 * 测试: 提交15个执行时间需要3秒的任务,看线程池的状况
 * @param threadPoolExecutor 传入不同的线程池,看不同的结果
 */
public void testCommon (ThreadPoolExecutor threadPoolExecutor) throws InterruptedException {
    for (int i = 0; i < 15; i++) {
        int n = i;
        threadPoolExecutor.submit(() -> {
            try {
                System.out.println("开始执行 : " + n);
                Thread.sleep(3000);
                System.out.println("执行结束 :" + n);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("任务提交成功 : " + i);
    }
    //查看线程数量, 查看队列等待数量
    Thread.sleep(500);
    System.out.println("当前线程池的线程数量为 : " + threadPoolExecutor.getPoolSize());
    System.out.println("当前线程池等待的任务数量为 : " + threadPoolExecutor.getQueue().size());
    //等待15秒,查看线程数量,查看队列数量(理论上,超出核心数量的线程会被自动销毁)
    Thread.sleep(15000);
    System.out.println("当前线程池的线程数量为 : " + threadPoolExecutor.getPoolSize());
    System.out.println("当前线程池等待的任务数量为 : " + threadPoolExecutor.getQueue().size());
}

示例1:标准线程池代码

/**
 * 1.线程池信息:核心线程数量5,最大数量10,超出核心线程数量的线程存活时间 :5秒,无界工作队列(队列中不限制任务数量),未指定拒绝策略
 */
private void threadPoolExecutorTest1() throws Exception {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
    this.testCommon(threadPoolExecutor);
    //预计结果,线程池线程数量为5,超出核心数量的任务,其他的进入工作队列中等待被执行
}

运行结果

任务提交成功 : 0
开始执行 : 0
......
当前线程池的线程数量为 : 5
当前线程池等待的任务数量为 : 10
执行结束 :0
......

示例2:指定拒绝策略的线程池代码

/**
 * 2.线程池信息:核心线程数量5,最大数量10,超出核心线程数量的线程存活时间 :5秒,队列大小3,指定拒绝策略
 */
private void threadPoolExecutorTest2() throws Exception {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(3),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.err.println("有任务被拒绝执行了");
                }
            });
    this.testCommon(threadPoolExecutor);
    //预计结果
    //1. 5个任务直接分配线程开始执行
    //2. 3个任务进入等待队列
    //3. 队列不够用,临时加开5个线程来执行任务(5秒没干活就销毁)
    //4. 队列和线程池都满了,剩下2个任务,没资源了,被拒绝执行
    //5. 任务执行,5秒后,无任务可执行,销毁临时创建的5个线程
}

运行结果

任务提交成功 : 0
开始执行 : 0
......
有任务被拒绝执行了
有任务被拒绝执行了
当前线程池的线程数量为 : 10
当前线程池等待的任务数量为 : 3
执行结束 :1
......
当前线程池的线程数量为 : 5
当前线程池等待的任务数量为 : 0

示例3:核心线程数量与最大线程数量一致时,运行过程与示例1一致

/**
	* 3.线程池信息:核心线程数量5,最大数量5,超出核心线程数量的线程存活时间 :5秒,无界工作队列(队列中不限制任务数量),未指定拒绝策略
  */
    private void threadPoolExecutorTest3() throws Exception {
        //和Executors.newFixedThreadPool(int nThread)的一样的
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());
        this.testCommon(threadPoolExecutor);
        //预计结果,线程池线程数量为5,超出核心数量的任务,其他的进入工作队列中等待被执行
    }

运行结果

任务提交成功 : 0
开始执行 : 0
......
有任务被拒绝执行了
有任务被拒绝执行了
当前线程池的线程数量为 : 10
当前线程池等待的任务数量为 : 3
执行结束 :1
......
当前线程池的线程数量为 : 5
当前线程池等待的任务数量为 : 0

示例4:核心线程数量为0,最大线程数量非常大,SynchronousQueue实际上它不是一个真正的队列,因为他不会为队列中元素维护存储空间。与其他队列不同的是,他维护一组线程,这些线程在等待着把元素加入或移出队列。在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,如果线程池中有空闲的线程,直接交给空闲的线程执行该任务;如果线程池中没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到最大线程池的大小maximumPoolSize),适用于无法预估任务数量的情况。

/**
     * 4.线程池信息:核心线程数量0,最大数量Integer.MAX_VALUE,超出核心线程数量的线程存活时间 :60秒,SynchronousQueue队列,未指定拒绝策略
     */
    private void threadPoolExecutorTest4() throws Exception {
        //SynchronousQueue实际上它不是一个真正的队列,因为他不会为队列中元素维护存储空间。
        //与其他队列不同的是,他维护一组线程,这些线程在等待着把元素加入或移出队列。
        //在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,
        //而线程池中又没有空闲的线程能顾从SynchronousQueue队列实例中取一个任务,
        //那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。
        //此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到最大线程池的大小maximumPoolSize)

        //和Executors.newCachedThreadPool()一样的
        //适合无法预估任务数量
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
        this.testCommon(threadPoolExecutor);
        //预计结果
        //1. 线程池线程数量为:15,超出数量的任务,其他的进入队列中等待被执行
        //2. 所有任务执行结束,60秒后,如果无任务可执行,所有线程全部被销毁,池的大小恢复为0
        Thread.sleep(60000);
        System.out.println("60秒后,再看线程池中的数量" + threadPoolExecutor.getPoolSize());

    }

运行结果

任务提交成功 : 0
当前线程池的线程数量为 : 15
当前线程池等待的任务数量为 : 0
执行结束 :0
当前线程池的线程数量为 : 15
当前线程池等待的任务数量为 : 0
60秒后,再看线程池中的数量0

示例5: 定时执行线程信息:3秒后执行,一次性任务,到点就执行

/**
     * 5. 定时执行线程信息:3秒后执行,一次性任务,到点就执行
     *     核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
     */
    private void threadPoolExecutorTest5() throws Exception {
        //和Executors.newScheduledThreadPool()一样的
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
        threadPoolExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务被执行,现在时间:" + System.currentTimeMillis());
            }
        }, 3000, TimeUnit.MILLISECONDS);
        System.out.println("定时任务,提交成功,时间是:" + System.currentTimeMillis());
        //预计结果:任务在三秒后被执行一次
    }

运行结果

定时任务,提交成功,时间是:1659331101205
任务被执行,现在时间:1659331104211

示例6: 定时执行线程信息:

  • threadPoolExecutor.scheduleAtFixedRate(): 提交后,两秒钟后第一次执行之后每隔一秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,完毕后立刻执行),也就是说这个代码中是,3秒重执行一次,(计算方式:每次执行3秒,间隔为1秒,执行结束后马上开始下一次执行,无需等待)。推荐使用。
  • threadPoolExecutor.scheduleWithFixedDelay():提交后,两秒钟后第一次执行之后,每隔一秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,等上一次执行完毕后再开始计时,等待1秒),也就是说这个代码的效果看到的是:4秒钟执行一次。(计算方式:每次执行3秒,间隔为1秒,执行完再等待1秒,所以3 + 1)。
/**
     * 6. 定时执行线程信息:线程固定数量5
     *    核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
     */
    private void threadPoolExecutorTest6() throws Exception {
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
        //周期性执行一个任务,线程池提供了两种调度方式,这里单独演示一下。测试场景一样
        //测试场景:提交的任务需要3秒才能执行完毕。看两种不同调度方式的区别。
        //效果1:提交后,两秒钟后第一次执行之后每隔一秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,完毕后立刻执行)
        //也就是说这个代码中是,3秒重执行一次,(计算方式:每次执行3秒,间隔为1秒,执行结束后马上开始下一次执行,无需等待)
        threadPoolExecutor.scheduleAtFixedRate(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务被执行-1,现在时间:" + System.currentTimeMillis());
        }, 2000,1000, TimeUnit.MILLISECONDS);
        System.out.println("定时任务-1,第一次提交成功,时间是:" + System.currentTimeMillis());

        //效果2:提交后,两秒钟后第一次执行之后,每隔一秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,等上一次执行完毕后再开始计时,等待1秒)
        //也就是说这个代码的效果看到的是:4秒钟执行一次。(计算方式:每次执行3秒,间隔为1秒,执行完再等待1秒,所以3 + 1)

        threadPoolExecutor.scheduleWithFixedDelay(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务被执行-2,现在时间:" + System.currentTimeMillis());
        }, 2000,1000, TimeUnit.MILLISECONDS);
        System.out.println("定时任务-2,第一次提交成功,时间是:" + System.currentTimeMillis());

    }

运行结果

  • 效果1
定时任务-1,第一次提交成功,时间是:1659331409362
任务被执行-1,现在时间:1659331414371
任务被执行-1,现在时间:1659331417376
任务被执行-1,现在时间:1659331420381
  • 效果2
定时任务-2,第一次提交成功,时间是:1659331494185
任务被执行-2,现在时间:1659331499197
任务被执行-2,现在时间:1659331503204
任务被执行-2,现在时间:1659331507210
任务被执行-2,现在时间:1659331511217
  • 一起执行
定时任务-1,第一次提交成功,时间是:1659331563439
定时任务-2,第一次提交成功,时间是:1659331563440
任务被执行-1,现在时间:1659331568450
任务被执行-2,现在时间:1659331568450
任务被执行-1,现在时间:1659331571457
任务被执行-2,现在时间:1659331572458
任务被执行-1,现在时间:1659331574463
任务被执行-2,现在时间:1659331576468
任务被执行-1,现在时间:1659331577464

示例7: 中止线程(shutdown),会等待所有执行中的线程的任务和等待队列中的任务执行结束。

/**
     * 7. 终止线程:线程池信息:核心线程数量5,最大数量10,,超出核心线程数量的线程存活时间 :5秒,队列大小3,指定拒绝策略
     */
    private void threadPoolExecutorTest7() throws Exception {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10,
                5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("有任务被拒绝执行了");
                    }
                });
        //测试: 提交15个执行时间需要3秒的任务,看超过大小的2个,对应的处理情况
        for (int i = 0; i < 15; i++) {
            int n = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("开始执行 : " + n);
                        Thread.sleep(3000);
                        System.out.println("执行结束 :" + n);
                    } catch (InterruptedException e) {
                        System.out.println("异常:" + e.getMessage());
                    }
                }
            });
            System.out.println("任务提交成功 : " + i);
        }
        //1秒后终止线程池
        Thread.sleep(1000);
        threadPoolExecutor.shutdown();
        //再次提交提示失败
        threadPoolExecutor.submit(() -> System.out.println("追加一个任务"));
        //结果分析
        //1. 10个任务被执行,3个任务进入队列等待,2个任务被拒绝执行
        //2. 调用shutdownNow后,不再接受新任务,等待13个任务执行结束
        //3. 追加的任务在线程池关闭后,无法再提交,会被拒绝执行
    }

运行结果

任务提交成功 : 0
开始执行 : 0
......
有任务被拒绝执行了
任务提交成功 : 13
有任务被拒绝执行了
任务提交成功 : 14
开始执行 : 12
有任务被拒绝执行了
执行结束 :0
......

示例8: 立刻中止线程(shutdownNow),会中断正在执行的任务,并且不执行等待队列中的任务,返回等待任务队列中的任务信息。

 /**
     * 8. 立刻终止线程:线程池信息:核心线程数量5,最大数量10,,超出核心线程数量的线程存活时间 :5秒,队列大小3,指定拒绝策略
     */
    private void threadPoolExecutorTest8() throws Exception {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10,
                5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("有任务被拒绝执行了");
                    }
                });
        //测试: 提交15个执行时间需要3秒的任务,看超过大小的2个,对应的处理情况
        for (int i = 0; i < 15; i++) {
            int n = i;
            threadPoolExecutor.submit(() -> {
                try {
                    System.out.println("开始执行 : " + n);
                    Thread.sleep(3000);
                    System.out.println("执行结束 :" + n);
                } catch (InterruptedException e) {
                    System.out.println("异常:" + e.getMessage());
                }
            });
            System.out.println("任务提交成功 : " + i);
        }
        //1秒后终止线程池
        Thread.sleep(1000);
        List<Runnable> shutdownNow = threadPoolExecutor.shutdownNow();
        //再次提交提示失败
        threadPoolExecutor.submit(() -> System.out.println("追加一个任务"));
        System.out.println("未执行的等待任务的数量:" + shutdownNow.size());
        //结果分析
        //1. 10个任务被执行,3个任务进入队列等待,2个任务被拒绝执行
        //2. 调用shutdownNow后,队列中的3个线程不再执行,10个线程被终止
        //3. 追加的任务在线程池关闭后,无法再提交,会被拒绝执行
    }

运行结果

任务提交成功 : 0
开始执行 : 0
......
有任务被拒绝执行了
任务提交成功 : 13
有任务被拒绝执行了
任务提交成功 : 14
开始执行 : 12
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
异常:sleep interrupted
有任务被拒绝执行了
未执行的等待任务的数量:3

线程数量

如何确定合适数量的线程?

  • **计算型任务:**cpu数量的1-2倍。
  • IO型任务:相对于计算型任务,需多一些线程,要根据具体的IO阻塞时长进行考量决定。如Tomcat中默认的最大线程数量为:200。也可以考虑根据需要再一个最小数量和最大数量间自动增减线程数。

CPU利用率达到80%,说明CPU进行了充分利用。
观测指标,不宜太高,也不宜太低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值