Executor框架

最近在看线程池相关的内容,之前总觉得这块很复杂,难以理解,近来跟打通了任督二脉一样,茅塞顿开。话不多说,直接附上最近的一些些收货。

Executor这个线程池框架中,有几个很典型的线程池。

  • 1)newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

  • 2)newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  • 3)newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  • 4)newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

下面附上图片一张用来介绍核心概念以及他们之间的差异

这几个核心参数,重中之重是需要把握corePoolSize与maximumPoolSize与当前正在运行的线程总数以及等待队列的容量的关系。假设当前corePoolSize的大小是1,maximumPoolSize=2,当前等待队列实现是blockQueue的大小是3。

下面跟着做一个小小的推演:

1.此时客户端发过来了1-4标号的4个任务。这四个任务先后的执行顺序

此时运行的过程是标号1的进入核心线程中,然后标号2,3,4的线程是进入等待队列中,当1执行完毕的时候,从等待队列中取出标号2的任务执行,依次类推。

2.如果客户端发过来了1-5标号的5个任务,执行顺序又当如何呢?

此时的执行顺序是1和5号同时执行,然后依次执行2,3,4任务。

why?此时是因为目前实际占用核心线程的只有1号,2,3,4在等待队列中,5号任务来的时候发现队列满了,但是当前的总线程数只有1号一个,它不大于maximunPoolSize,所以就直接创建新的线程执行任务。

3.如果客户端发过来了1-6标号的6个任务,此时会出现什么状况?

此时,1.5号任务还是会几乎同时执行,然后抛出一个RejectedExecutionException的错误,然后2,3,4好任务依次执行。1号5号2个任务已经达到了线程池的最大值,2,3,4还是在等待队列中,此时6号任务来了之后,发现线程池中满了,等待队列中也满了,已经没有能力再来执行6号任务。于是线程池会直接抛出一个拒绝6号任务的Exception。

下面附上代码:

public class Task implements Runnable{
    private Integer taskId;
    private String taskName;

    public Integer getTaskId() {
        return taskId;
    }

    public void setTaskId(Integer taskId) {
        this.taskId = taskId;
    }

    public String getTaskName() {
        return taskName;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public Task(Integer taskId, String taskName) {
        this.taskId = taskId;
        this.taskName = taskName;
    }

    @Override
    public void run() {
        try {
            System.out.println("run taskId =" + this.taskId);
            Thread.sleep(5000L);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return "Task{" +
                "taskId=" + taskId +
                ", taskName='" + taskName + '\'' +
                '}';
    }

然后是执行的main方法

public class MyThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(3));//,
               // new Rejected());

        Task mt1 = new Task(1,"任务1");
        Task mt2 = new Task(2, "任务2");
        Task mt3 = new Task(3, "任务3");
        Task mt4 = new Task(4, "任务4");
        //任务五
        Task mt5 = new Task(5, "任务5");
        Task mt6 = new Task(6, "任务6");

        pool.execute(mt1);
        pool.execute(mt2);
        pool.execute(mt3);
        pool.execute(mt4);
        pool.execute(mt5);
        pool.execute(mt6);

        pool.shutdown();
    }
}

附加:当然当线程无能力执行多余的任务的时候,程序直接报错,肯定是不大友好的,可以自己写一个类继承RejectedExecutionHandler,然后根据需要写上友好处理的代码。

public class Rejected implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(Thread.currentThread().getName());
        System.out.println("打回请求,记录日志");
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值