快速掌握ThreadPoolExecutor--Java多线程并发编程(上编)

ThreadPoolExecutor源码分析(上编)

一. ThreadPoolExecutor应用方式

  • 基本应用方式举例:

    //创建线程池
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
        //1. 核心线程数
        2,
        //2. 最大线程数
        3,
        //3. 最大线程数的存活时间
        500,
        //4. 时间单位
        TimeUnit.SECONDS,
        //5. 阻塞队列
        new LinkedBlockingDeque<>(),
        //6. 线程工厂
        new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("线程名字1");
                return t;
            }
        },
        //7. 拒绝策略
        new ThreadPoolExecutor.AbortPolicy()
    );
    //调用线程池线程执行的方法execute()和submit()
    //execute执行线程没有返回结果,只能走Runnable接口
    //submit执行线程可以有返回结果,可用callable接口,通过返回的对象,调用get()获取返回的值(注意这个位置会阻塞住)
    poolExecutor.execute(() ->
                         System.out.println("执行测试1----线程"));
    }
    
  • 思考:

    1. 核心线程应用写多大合适?
    2. 最大线程怎么设置?
    3. 阻塞队列怎么设置比较合适?
    4. 拒绝策略怎么选择合适?
    5. 如何将当前系统的硬件CPU,发挥出它的最大的特性?
  • 带着以上的几个问题我们一起来找下答案…

二. ThreadPoolExecutor核心参数

  • 进入ThreadPoolExecutor类中查看源码,可以看到包含以下7个参数:

  • public ThreadPoolExecutor(int corePoolSize,     //1.核心线程数
                              int maximumPoolSize,  //2.最大线程数
                              long keepAliveTime,   //3.最大线程数的存活时间
                              TimeUnit unit,        //4.时间单位
                              BlockingQueue<Runnable> workQueue,//5.阻塞队列
                              ThreadFactory threadFactory,//6.线程工厂
                              RejectedExecutionHandler handler)//7.拒绝策略
    {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
            null :
        AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    
  • corePoolsize 核心线程数

    指的是线程池中能执行的线程个数

  • maximumPoolSize 最大线程数(非核心线程)

    指的是线程池中,当核心线程数满以及阻塞队列满时,整个线程池中最大的执行线程数量

  • keepAliveTime 最大线程数的存活时间

    非核心线程的存活时间

  • TimeUnit 时间单位

    keepAliveTime 的时间单位

  • BlockingQueue 阻塞/工作队列

    1. 核心线程没有空闲的时候就会扔到这个队列中进行排队

    2. BlockingQueue 是一个接口,常用实现类是:

      ArrayBlockingQueue 可以指定长度的

      LinkedBlockingQueue 没有指定长度的

  • ThreadFactory 线程工厂

    1. 线程池默认是空的,只有第一次提交任务的时候才会创建线程
    2. 就是通过这个线程工厂将线程创建出来的
    3. 只不过Thread对象被封装成了一个Work对象
  • RejectedExecutionHandler 拒绝策略

    1. 当核心线程数,阻塞队列,最大线程数均执行满时,才会执行这个拒绝策略

    2. RejectedExecutionHandler 是一个接口,因此可以引用它的实现类

      AbortPolicy 直接抛出异常

      CallerRunsPolicy 当线程池中的线程没有空闲时,调用的主线程自己执行线程

      DiscardOldestPolicy 把阻塞队列中排在最前面的任务弹出去,再执行线程

      DiscardPolicy 当线程池线程没有空闲时,直接忽略。当然也可以重写它的方法执行一定的业务

    3. 可以根据实际的业务需求,去实现其拒绝的策略

三. ThreadPoolExecutor执行流程

  • 业务线程提交任务到线程池之后,任务的处理流程

  • 可通过查看 execute( ) 的源码,进行分析:

  • public void execute(Runnable command) {
    	//判断传递进来的线程是否为空值
        if (command == null)
            throw new NullPointerException();
        //获得AtomicInteger对象
        int c = ctl.get();
        //通过workerCountOf(c) 获取“工作线程个数”来与“核心线程数”进行判断
        if (workerCountOf(c) < corePoolSize) {
        	// 如果小于核心线程数,addWorker(command, true)则添加当前线程到工作线程中执行线程
            if (addWorker(command, true))
                //添加核心线程成功则返回true
                return;
            //如果在并发的情况下,核心线程数添加失败,则会重写获取ctl属性,继续往下执行
            c = ctl.get();
        }
        //如果上面的条件不满足则会继续进行下面的判断
        //isRunning(c)判断线程是否RUNNING状态,并且通过offer()将线程放入队列中,会判断工作队列是否排满,成功返回true,失败返回false
        if (isRunning(c) && workQueue.offer(command)) {
        	//将任务放到工作队列,等待工作线程执行
            
            //这里会再次获取ctl,重写检查线程的状态
            int recheck = ctl.get();
            //判断线程是否RUNNING状态取反,表示如何不是RUNNING状态,就会将任务从工作队列中移除
            if (! isRunning(recheck) && remove(command))
                // 移除成功则会执行拒绝策略
                reject(command);
            //如果不为上面的情况则会再次判断,工作线程是否为0
            else if (workerCountOf(recheck) == 0)
                //这一步是为了解决,工作线程数为0,但是工作队列有任务在排队的情况
                //因此会添加一个“空任务非核心线程”
                addWorker(null, false);
        }
        //任务添加到工作队列失败,则会执行这个
        //addWorker(command, false)判断工作线程数是否大于最大线程数.创建非核心线程并执行当前任务
        //其中false表示添加非核心线程,true表示核心线程
        else if (!addWorker(command, false))
            // 取反,表示添加非核心线程失败,开始执行拒绝策略
            reject(command);
    }
    
    • 执行流程图:
      图片类源于:【马士兵教育】多线程视频
      图片类源于:【马士兵教育】多线程视频

四. ThreadPoolExecutor状态

  • 了解其状态,就要先了解线程池中的核心属性 ctl ,线程的状态是通过其计算得出来的
//其中ctl本质就是一个int类型的数值
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE = 32,则COUNT_BIT = 29 (表示标识工作线程个数)
//为什么这样表示-----主要是因为ctl表述了两个状态:
//1.(高三位)表示线程池当前的状态
//2.(低29位)标识线程池当前的工作线程个数 
private static final int COUNT_BITS = Integer.SIZE - 3;
//CAPACITY表示工作线程的最大数量(包括:核心线程及非核心线程)=(1右移29位)- 1;
//二进制表示是:00011111 11111111 11111111 11111111 = 4160486911 (转十进制)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 以下5种就是(高三位)表示线程池的状态(通过右移二进制转换得到以下二进制值)
private static final int RUNNING    = -1 << COUNT_BITS; //111
private static final int SHUTDOWN   =  0 << COUNT_BITS; //000
private static final int STOP       =  1 << COUNT_BITS; //001
private static final int TIDYING    =  2 << COUNT_BITS; //010
private static final int TERMINATED =  3 << COUNT_BITS; //011

//计算当前线程池的状态;
//c表示当前ctl对象,~CAPACITY 工作线程数取反,& 二进制运算符
//比如:当前c为000,~CAPACITY取反为111,因此 000 & 111 = 000(&运算都为1才为1)
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//计算当前线程池中的工作线程个数
private static int workerCountOf(int c)  { return c & CAPACITY; }
  • 线程池的状态变换
    图片类源于:【马士兵教育】多线程视频
    图片类源于:【马士兵教育】多线程视频
  1. 从RUNNING状态到SHUTDOWN状态是通过调用shutdown( )进入的,并加锁停止接收任务
  2. 从RUNNING状态到STOP状态是通过调用shutdownNow( ),直接中断任务(暴力停止)
  3. 而TIDYING是一个过渡状态是shutdown或shutdownNow方法内部的调用,是通过执行tryTerminate( )进行调用的,会判断工作线程数是否为0
  4. TERMINATED销毁状态。是执行完tryTerminate( ),会自动调用terminated(){}进行销毁线程,其中这个方法业务逻辑是空的,可以根据业务需要重写这个方法,线程销毁之后需要做处理的业务逻辑
  5. 注意:每次提交任务都会判断当前线程的状态,是因为往线程池提交任务是有并发操作的,这种并发操作可能会造成任务刚提交,忽然之间调用shutdown( ),因此需要每次都重写判断当前任务线程的状态。以此来避免并发的一些问题。
  1. 以上是对ThreadPoolExecutor类基本的一个解析,并对execute( ) 的源码进行初步的分析。相信大家对创建线程池的的这类,已经有基本的了解啦!

  2. 其中涉及到的addWorker方法, Worker对象,runWorker方法,getTask方法,processWorkerExit方法 ,并没有深度分析,将会在ThreadPoolExecutor源码分析(下编)中继续阐述。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值