线程池

一、线程池介绍

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

如果用生活中的列子来说明,我们可以把线程池当做一个客服团队,如果同时有1000个人打电话进行咨询,按照正常的逻辑那就是需要1000个客服接听电话,服务客户。现实往往需要考虑到很多层面的东西,比如:资源够不够,招这么多人需要费用比较多。正常的做法就是招100个人成立一个客服中心,当有电话进来后分配没有接听的客服进行服务,如果超出了100个人同时咨询的话,提示客户等待,稍后处理,等有客服空出来就可以继续服务下一个客户,这样才能达到一个资源的合理利用,实现效益的最大化。

二、Java中的线程池种类

  • newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。

  • newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。

  • newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。

  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

1.自定义一个线程类:

public class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");
        
    }

}

2.测试类

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 1.单线程的线程池
        ExecutorService singlePool = Executors.newSingleThreadExecutor();
        // 2.固定大小的线程池
        ExecutorService fixedPool = Executors.newFixedThreadPool(10);
        // 3.可缓存的线程池
        ExecutorService cacheddPool = Executors.newCachedThreadPool();
        // 4.定时任务的线程池
        ExecutorService scheduleddPool = Executors.newScheduledThreadPool(10);
        // 5.任务队列的线程池jdk1.8才有
        ExecutorService stealingdPool = Executors.newWorkStealingPool();

        // 自定义线程池的拒绝策略
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(// 使用ThreadPoolExecutor自定义线程池
                10, 100, 10, TimeUnit.SECONDS, workQueue, new RejectedExecutionHandler() {

                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                        if (e.isShutdown()) {
                            r.run();
                        }

                    }
                });
        for (int i = 0; i < 20; i++) {
            // singlePool.execute(new MyThread());
            // fixedPool.execute(new MyThread());
            // cacheddPool.execute(new MyThread());
            // scheduleddPool.execute(new MyThread());
            // stealingdPool.execute(new MyThread());
            // executor.execute(new MyThread());
            Future<?> submit = singlePool.submit(new MyThread());
            System.out.println(submit);
        }
    }

}

3.线程池的拒绝策略

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

  1. AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
  2. CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
  3. DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
  4. DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。

  除了JDK默认为什么提供的四种拒绝策略,我们可以根据自己的业务需求去自定义拒绝策略,自定义的方式很简单,直接实现RejectedExecutionHandler接口即可

比如Spring integration中就有一个自定义的拒绝策略CallerBlocksPolicy,将任务插入到队列中,直到队列中有空闲并插入成功的时候,否则将根据最大等待时间一直阻塞,直到超时。

4.execute和submit的区别

  execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了

  submit方法适用于需要关注返回值的场景

public interface ExecutorService extends Executor {
  ...
  <T> Future<T> submit(Callable<T> task);
  <T> Future<T> submit(Runnable task, T result);
  Future<?> submit(Runnable task);
  ...
}

  其子类AbstractExecutorService实现了submit方法,可以看到无论参数是Callable还是Runnable,最终都会被封装成RunnableFuture,然后再调用execute执行。

5.线程池的关闭

  关闭线程池可以调用shutdownNow和shutdown两个方法来实现

  shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表

  shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

  还有一些业务场景下需要知道线程池中的任务是否全部执行完成,当我们关闭线程池之后,可以用isTerminated来判断所有的线程是否执行完成,千万不要用isShutdown,isShutdown只是返回你是否调用过shutdown的结果。

6.自定义线程池

在实际的使用过程中,大部分我们都是用Executors去创建线程池直接使用,如果有一些其他的需求,比如指定线程池的拒绝策略,阻塞队列的类型,线程名称的前缀等等,我们可以采用自定义线程池的方式来解决。

如果只是简单的想要改变线程名称的前缀的话可以自定义ThreadFactory来实现,在Executors.new…中有一个ThreadFactory的参数,如果没有指定则用的是DefaultThreadFactory。

自定义线程池核心在于创建一个ThreadPoolExecutor对象,指定参数

下面我们看下ThreadPoolExecutor构造函数的定义:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) ;
    • corePoolSize
      线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。

    • maximumPoolSize
      最大线程数,线程池能创建的最大线程数量。

    • keepAliveTime
      在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。

    • unit
      时间单位

    • workQueue
      存放来不及处理的任务的队列,是一个BlockingQueue。

    • threadFactory
      生产线程的工厂类,可以定义线程名,优先级等。

    • handler
      拒绝策略,当任务来不及处理的时候,如何处理, 前面有讲解。

  阿里java编码规范明确说明:禁止使用Executors创建线程池,而应该使用ThreadPollExecutor来创建,并且使用ThreadFactory给出线程名称,以便跟踪。这样做的好处:让创建线程池的开发人员明确线程池运行规则,避免资源耗尽!

  1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM(Out Of Memory)。

  2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

所以,明确指名线程池核心线程数、最大线程数,以及队列最大持有任务数,能够更自主控制线程池。

  参考:https://mp.weixin.qq.com/s/5dexEENTqJWXN_17c6Lz6A

     ThreadFactory与BlockingQueue

posted @ 2018-07-10 18:25  灬小乙  阅读(205)  评论(0编辑  收藏  举报