java多线程学习(3)-线程池

时间:2022-06-04
本文章向大家介绍java多线程学习(3)-线程池,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

简介

随着并发的增多,创建、销毁线程的动作也随之增多,所以资源的浪费也随之增多,并且线程的数量变大,管理的难度也会随之加大------于是线程池小伙伴就出来前言

线程池的几个好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

看到这些好处有点心动,但是我们不仅要知其然还要知其所以然

如何使用线程池?

创建线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(
                                                corePoolSize, 
                                                maximumPoolSize,
                                                keepAliveTime, 
                                                milliseconds,
                                                runnableTaskQueue,
                                                threadFactory,
                                                handler);

参数的含义

  • corePoolSize:基本线程池,也叫核心线程池,一般活动的线程数量不会超过此参数的大小;
  • maximumPoolSize:当前线程池允许创建的最大线程数;
  • keepAliveTime:线程活动保持时间,当线程空闲下来时,控制线程存活的时间,当任务执行时间短,任务多,可以提高当前参数的大小,保证线程的利用率
  • milliseconds:时间单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
  • runnableTaskQueue:任务队列,用于保存等待执行的任务的阻塞队列,
  • threadFactory:用于创建线程的工厂,可以设置线程的名称
  • handler:饱和策略,当线程池和队列都饱和的 状态下,必须采取一种策略来处理新提交的任务;

如何提交任务?

executor()

我们可以使用executor向线程池提交任务,但是此种方式没有返回值,无法判断任务是否已经执行成功,参数为runable对象实例;

submit()

此方法不为ThreadPoolExecutor类自有的方法,他是属于ThreadPoolExecutor父类方法,返回future通过get判断任务是否执行成功,get会阻塞直到任务完成,源码如下

submit()
public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

不难发现提交任务是传入了ftask对象,看看newTaskFor()

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

返回的一个FutureTask实例

查看对应的get方法,返回一个int类型的值

如何关闭线程池

shutdown和shutdownNow

shutdown是将线程池的状态设置为shutdown状态,但是并不会停止正在工作的线程,shutdownNow将线程池的状态设置为stop状态,并且尝试停止正在执行任务的线程

线程池执行的原理

线程池流程分析

当线程池当中有新提交的任务时,判断流程如下:

  1. 基本线程池是否满了?没满,创建工作线程执行任务,满了,继续下面的判断
  2. 判断工作队列是否满了?没满,放入工作队列等待执行,满了,向下执行
  3. 判断线程池是否达到最大线程数?没有,创建新的工作线程,执行任务,满了,按照饱和策略处理
源码
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //判断是否小于基本线程池
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程池正在运行并且添加到工作队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //如果添加时的瞬间有人调用了shutdown方法的应急措施
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

通俗理解

一家工厂,有一个厂房,厂房里有5个工人(基本线程),每个工人负责处理一个机器(任务),这个厂房最多能容纳10个人(最大线程数),这个工厂还有一个仓库,仓库可以存储5个机器,当工人处理一个机器之后,会从仓库拿一个机器继续干活,当需求比较大的时候,老板发现仓库安置不下了,于是将机器直接放到厂房了,同时新招了几个人(总共不超过10个人),在厂房干活,当最后同时有超过15台机器的时候,没地方,也没人了,老板直接不接这个新的单子了(某种饱和策略)。