Java线程池相关参数以及拒绝策略

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kuangsonghan/article/details/81625097

为什么要使用线程池

在JavaSE中,要实现多线程的方式有多种,例如继承Thread、实现Runnable接口或者Callable接口。但是我们应该要知道,创建一个线程的开销是很大的,因为它总涉及与操作系统交互,我们知道,线程总共有三个过程,分别是创建、使用、销毁。但是在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。

线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。

也就是说,如果一个线程的创建时间为T1,,执行任务时间为T2,销毁时间为T3,如果T1+T2>T3,那么我们就需要使用池化的技术来尽可能缩短T1和T3,从而提高服务器性能,把T1和T3分别安排到服务器启动和技术或者一些空闲阶段,这样,服务器在处理请求时就不会有T1和T3的开销了。

使用线程池有如下有点:

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

线程池的工作原理

ThreadPoolExecutor的构造方法

当向线程池中提交一个任务后,线程池是如何来处理这个任务的?Java提供一个线程池ThreadPoolExecutor类,我们从ThreadPoolExecutor的构造函数来分析一下这个线程池的使用方法。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

在上面的构造方法中,在该方法中有7个参数,我分别和说说这7个参数

  1. corePoolSize: 线程池维护线程的最少数量(也叫核心线程池数量)
  2. maximumPoolSize:线程池维护线程的最大数量
  3. keepAliveTime: 线程池维护线程所允许的空闲时间
  4. unit: 线程池维护线程所允许的空闲时间的单位
  5. workQueue: 线程池所使用的缓冲队列
  6. threadFactory:线程创建的工厂
  7. handler: 线程池对拒绝任务的处理策略

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

线程池的工作流程

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

  1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
  3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
  5. 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

举个例子来说明一下线程池中的工作流程:

假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

线程池的拒绝策略 

当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

  • AbortPolicy
  • DiscardPolicy
  • DiscardOldestPolicy
  • CallerRunsPolicy
  • 自定义

  来分别说说这五种拒绝策略

1.AbortPolicy:该策略是线程池默认的策略,使用策略时,如果线程池的线程数满了,如果再有一个的任务进来,那么线程池会丢掉这个任务并且抛出RejectedExecutionException异常。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            //不做任何处理,直接抛出异常
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

2.DiscardPolicy:这个策略和AbortPolicy的slient版本,如果线程池队列满了,再有新的任务进来,线程池则会直接丢掉这个任务并且不会有任何异常。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        	//就是一个空的方法
        }

3.DiscardOldestPolicy:这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
            	//移除队头元素
                e.getQueue().poll();
                //再尝试入队
                e.execute(r);
            }
        }

4.CallerRunsPolicy:使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                //直接执行run方法
                r.run();
            }
        }

5.自定义策略,如果在使用过程中,Java对我们提供给我们的四种策略都不符合我们的要求,那我们可以自定义策略。自定义需要实现RejectedExecutionHandler接口,并重写其rejectedExecution方法,具体的策略代码我们写在rejectedExecution方法中就可以了。

public class MyRejectPolicy implements RejectedExecutionHandler{
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        //Sender是我的Runnable类,里面有message字段
        if (r instanceof Sender) {
            Sender sender = (Sender) r;
            //直接打印
            System.out.println(sender.getMessage());
        }
    }
}

 

没有更多推荐了,返回首页