线程池ThreadPoolExecutor的详解

上一篇我已经介绍了开发中四种常用的线程池的用法,他们都会得到一个ExecutorService的实现类,也就是ExecutorService才是真正的线程池,现在来说一说他的实现类ThreadPoolExecutor

线程的几种状态,四种常见的线程池的介绍

1. ThreadPoolExecutor和newSingleThreadExecutor等四种线程池之间的关系

其实看源码我们可以知道,

 ExecutorService pool = Executors.newSingleThreadExecutor();

Executors明显是一个工具类,对于线程池这么复杂的概念来说,工具类的存在就是让开发人员简易的使用线程池,我们来看看这个工具类的源码

	/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
     
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

可以看出其实根本还是ThreadPoolExecutor对象,其他的三个常用线程池也是一样.

2.线程池ThreadPoolExecutor的创建

ThreadPoolExecutor 是 ExecutorSerivce接口的具体实现

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        3,6,2, TimeUnit.SECONDS ,
        new ArrayBlockingQueue<Runnable>(1),
        new ThreadPoolExecutor.AbortPolicy()
    );

要传入6个参数, 6个参数分别为:
(1)corePoolSize: 核心池的大小, 或者说线程池维护线程的最少数量 (core : 核心)
(2)maximumPoolSize: 线程池中线程的最大数量
(3)keepAliveTime: 线程池维护线程所允许的空闲时间
(4)unit: 线程池维护线程所允许的空闲时间的单位
(5)workQueue: 线程池所使用的缓冲队列
(6)handler: 线程池对拒绝任务的处理策略

handler有四个选择:
ThreadPoolExecutor.AbortPolicy(): 抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy(): 重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy(): 抛弃当前的任务

线程池ThreadPoolExecutor的详解_第1张图片

当一个任务通过execute(Runnable)方法欲添加到线程池时:

  • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

  • 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

所以线程池处理任务的优先级是:

核心线程池 >>> 等待序列  >>> 非核心线程池 >>> 拒绝或其他处理措施

想象成工厂的话,核心线程池就是勤奋的工人,有工作来了自主上去工作,然后工作越来越多,工人忙不过来,就放在一边等着(workQueue),然后工作继续变多,老板不得不临时找一些临时工(maximumPoolSize-corePoolSize)来帮忙,然后工作还在变多,老板也没钱请工人了,就回绝(handle)了甲方的任务

线程池ThreadPoolExecutor的详解_第2张图片

3. 如何使用

继承Thread类, 写一个自己的线程对象

package com.zgd.thread;

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行中。。。");
        // 等待一段时间,便于观察
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        super.run();
    }
}

创建ThreadPoolExecutor类

/**
 * 使用ThreadPoolExecutor线程池
 */
public static void poolExecutor(){
    // 创建一个线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            3,6,2, TimeUnit.SECONDS ,
            new ArrayBlockingQueue(1),
            new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0;i<8;i++ ){
        MyThread myThread = new MyThread();
        threadPoolExecutor.execute(myThread);
    }
}

运行结果
线程池ThreadPoolExecutor的详解_第3张图片

解析
这里设置的参数中, 核心池数量是3 , 最大线程大小是6 , 每个线程维护时间是 2s, 队列大小是 1

循环8次 :
首先3次 , 核心池没满 , 创建3个线程
第4次, 核心池满了, 队列没满, 任务放在队列中等待 这里从结果中可以看出放在队列中的是tread-2执行的任务
第5, 6,7 次, 核心池满了, 队列也满了 , 继续创建线程3个 , 达到最大线程数量.
第8次, 因为设置了sleep , 前面的6个线程都没有执行完毕 , 执行策略, 我这里设置的是ThreadPoolExecutor.AbortPolicy , 直接抛出异常
线程sleep时间到了, 执行任务完毕, 队列中的任务获得线程执行

关于线程池大小可以参考我的另一篇文章

https://blog.csdn.net/zzzgd_666/article/details/82842839


举例: 阿里的的代码检查插件Alibaba Java Coding Guidelines建议不要用ExcutorService创建线程池,给出的方案如下:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

Positive example 1//org.apache.commons.lang3.concurrent.BasicThreadFactory
    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
       
Positive example 2//线程创建工厂,可以自定义线程命名格式
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
		.setNameFormat("demo-pool-%d").build();

//ThreadPoolExecutor.AbortPolicy()如果任务数量超出线程池处理能力,抛出java.util.concurrent.RejectedExecutionException异常
ExecutorService pool = new ThreadPoolExecutor(5, 200,
		0L, TimeUnit.MILLISECONDS,
		new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Positive example 3:
    
        
        
        

    
        
            
        
    
    //in code
    userThreadPool.execute(thread);
       
        
 

你可能感兴趣的:(线程,线程)