zoukankan      html  css  js  c++  java
  • 多线程之线程池(上)

    前言

    最近一段时间,我们一直都在分享多线程相关知识,也一直有用线程池,但是一直没有介绍线程池相关知识,所以今天我们就先来看下线程池相关的知识点。

    线程池

    ThreadPool,线程池,顾名思义就是存放线程的池子,也是jdk1.5引入的。对我们而言,它的最主要优势就是简化了线程启动流程,让我们可以更方便地使用多线程,再也不用手动start线程,直接通过线程池提交我们的任务即可,而且合理使用线程池至于可以带来以下几个好处:

    • 降低资源消耗:复用线程,降低创建和销毁线程带来的资源消耗

    • 提高响应速度:使用线程池,省去了线程创建和初始化过程,所以任务可以更快执行

    • 提高线程的可管理性:可以直接通过线程池管理、监控、调度线程,线程管理更方便

    常用线程池

    常用的线程池有SingleThreadExecutorCachedThreadPoolScheduledThreadPoolFixedThreadPool,他们分别是单线程调度器,缓存线程池,定时任务线程池和固定线程池,他们都可以通过Executors创建,调用对应的静态方法即可,由于这一块的内容比较多,所以今天就简单提一下,后面专门讲一次。

    自定义线程池

    我们今天着重讲下自定义线程池,自定义线程池也很简单,直接new ThreadPoolExecutor() ,然后传入对应的参数即可,大家可以看下下面的示例:

    int corePoolSize = 10;
    int maximumPoolSize = 20;
    long keepAliveTime = 1000;
    TimeUnit unit = TimeUnit.MICROSECONDS;
    BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    for (int i = 0; i < 50; i++) {
        threadPoolExecutor.execute(() -> {
            String name = Thread.currentThread().getName();
            System.out.println("hello threadPool: "+ name);
        });
    }
    threadPoolExecutor.shutdown();
    

    ThreadPoolExecutor有三个构造方法,至少需要三个参数

    其中第一个参数是线程池的基本大小,当你提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使又空闲的线程存在,线程池依然会启动一个新线程来执行当前任务,直到线程池中的线程数达到线程基本大小(corePoolSize);

    第二个参数是线程池允许创建的最大线程数。如果工作队列(第三个参数)满了,且创建线程数已达到线程池基本大小,则线程池会继续创建新的线程来执行任务。如果你指定的工作队列是无界的,那这个参数也就失效了。

    第三个参数就是线程池工作队列,就是当你需要执行的任务超过线程池基本大小的时候,会把超出部分放进工作队列,等待线程池基本线程资源释放。

    下面我们分别验证以上三点,运行上面的示例代码:

    在第一次循环的时候(i=0),我们发现线程池的size0,活动线程数也是0,任务队列也是0,完成任务数也是0,这也说明线程池在最开始的时候是没有创建线程的;

    然后我们让他循环到第9次(i=8),这时候线程池已经被初始化,有8个线程(由于断点的原因,第9个线程尚未被创建),活动线程数5,完成执行的任务数3,任务队列还是0,说明确实在未达到线程池基本大小时,会不断创建新的线程;

    我们继续执行,让他循环到第15次(i=14),可结果似乎和我们预期不一样,按照预期,线程池的size应该是10,活动线程数也是10,任务队列也是4,完成任务数可能不确定,所以这里肯定不能通过debug的方式来看了,因为debug停顿之后好多线程资源已经被释放,任务根本就不会堆积,所以任务队列就不会有数据:

    所以这里我在线程池启动任务前加一行打印,打印线程池数据

    然后在运行,就可以拿到运行数据:

    这样的数据才是真实的,因为实际运行的时候,线程启动是非常快的,所以执行完成的任务数应该是0,等待任务数是4

    我们前面设定的最大线程数是20,但是翻看运行记录,我发现线程池的大小始终是10,说明只要不打到任务队列的上限,并不会创建新的线程,这里我们把循环次数改为60,然后运行下:

    但是依然没有创建新的线程,因为还是内没有达到任务队列上限,我们把循环次数再调大一点,调到70:

    现在线程池的大小就变成了19,活动线程数19,但是这时候如果你继续调大循环次数,线程池就会报错了:

    这个错误的原因就是线程池资源已经耗尽了,无法再接收新的任务了,这也就是说线程池能够处理的最大任务数是corePoolSize + maximumPoolSize + workQueue.size() ,当然,如果你的workQueue不设定大小,那永远都不会报这个错误,当然maximumPoolSize 也就无效了。

    总结

    原本打算线程池一次分享完的,但是实际分享过程中发现内容太多了(已经一千五百字了),所以今天就先到这里,明天再继续分享线程池其他内容。总的来说,今天的内容已经说明白了线程池很多基础的知识点(反正我自己觉得我都有好多收获),算是干货满满吧,你如果掌握了这些知识点,至少在使用线程池的过程中会少踩好多坑。好了,今天就先说这么多吧!

  • 相关阅读:
    c#委托总结
    架构研究一(autofac 注册路由 )
    Fedora20 和ubuntu 14.04 chrome标签中文乱码
    Fedora20 编译安装qemu-system
    NFS安装配置
    Mysql自动备份脚本
    Mysql性能调优(my.cnf参数篇)
    Mysql性能基本测试
    mysql编译安装主从复制
    Mysql 配置参数详解以及优化配置
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/15015057.html
Copyright © 2011-2022 走看看