学无先后,达者为师

网站首页 编程语言 正文

Executor 线程池技术详解

作者:wu1308156206 更新时间: 2022-07-10 编程语言

线程池3大方法

  1. 3种类型的线程池

    • 单一线程线程池

      newSingleThreadExecutor

      public class ThreadPoolTest {
          public static void main(String[] args) {
              //创建一个只有一个线程的线程池
              ExecutorService executorService = Executors.newSingleThreadExecutor();
      
              try{
                  for(int i = 1;i <= 20;i++){
                      executorService.execute(()->{
                          System.out.println(Thread.currentThread().getName()+" ok");
                      });
                  }
              }
              finally {
                  //线程池在不使用后 必须shutdowm
                  executorService.shutdown();
              }
          }
      }
      

      打印

    在这里插入图片描述

    单一线程的线程池,都是一个线程在执行。

    • 固定个数线程池

      newFixedThreadPool

      public class ThreadPoolTest {
          public static void main(String[] args) {
              //创建一个固定个数的线程池
              ExecutorService executorService = Executors.newFixedThreadPool(5);
      
              try{
                  for(int i = 1;i <= 20;i++){
                      executorService.execute(()->{
                          System.out.println(Thread.currentThread().getName()+" ok");
                      });
                  }
              }
              finally {
                  //线程池在不使用后 必须shutdowm
                  executorService.shutdown();
              }
          }
      }
      

      打印

在这里插入图片描述

 固定个数的线程池,永远只有这几个线程在执行
  • 可伸缩的线程池

    newCachedThreadPool

    public class ThreadPoolTest {
        public static void main(String[] args) {
            //创建一个可伸缩的线程池
            //根据当前任务数调整线程数量
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            try{
                for(int i = 1;i <= 20;i++){
                    executorService.execute(()->{
                        System.out.println(Thread.currentThread().getName()+" ok");
                    });
                }
            }
            finally {
                //线程池在不使用后 必须shutdowm
                executorService.shutdown();
            }
        }
    }
    

    打印

    在这里插入图片描述

    可伸缩的线程池,线程数根据任务数变化。

线程池7大参数

  1. 先来看上面3种线程池的构造方法

    //newSingleThreadExecutor
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    //newFixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    //newCachedThreadPool
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    //上面的线程池创建会调用这个方法
    //使用的默认线程工厂和默认的拒绝策略
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
    //最终调用的是这个方法
    public ThreadPoolExecutor(int corePoolSize,			//核心线程数
                              int maximumPoolSize,		//最大线程数
                              long keepAliveTime,		//线程最大空闲存活时间
                              TimeUnit unit,			//时间单位
                              BlockingQueue<Runnable> workQueue,	//阻塞队列
                              ThreadFactory threadFactory,		//线程创建工厂
                              RejectedExecutionHandler handler	//当线程池和阻塞队列都满了时的拒绝策略
                             ) {
        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;
    }
    

    从上面源码可以看出,

    newSingleThreadExecutor和newFixedThreadPool的线程数量虽然是固定的,但是阻塞队列用的是new LinkedBlockingQueue()没有设置大小,如果一直往里面添加任务的话,可能会出现OOM异常。

    newCachedThreadPool使用的阻塞队列虽然是new SynchronousQueue()同步队列,但是设置的最大线程数是Integer.MAX_VALUE,如果使用不当会把CPU资源耗尽。

    所以上面3种创建线程池的方法都不安全,可以自定义线程池。

  2. 自定义线程池

    public class ThreadPoolTest {
        public static void main(String[] args) {
            /**
             * 自定义线程池
             * 2个核心线程
             * 最大线程数5个
             * 最大空闲时间3
             * 时间单位秒
             * 阻塞队列为LinkedBlockingDeque 长度为3
             * 线程工程 Executors.defaultThreadFactory
             * 拒接策略 new ThreadPoolExecutor.AbortPolicy()   抛出异常
             */
            ExecutorService executorService = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingDeque<Runnable>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
                    );
    
            int taskNum = 2;
    
            try{
                for(int i = 1;i <= taskNum;i++){
                    for(int i = 1;i <= taskNum;i++){
                        final int tp = i;
                        executorService.execute(()->{
                            System.out.println(Thread.currentThread().getName()+" ok,task" + tp);
                        });
                    }
                }
            }
            finally {
                //线程池在不使用后 必须shutdowm
                executorService.shutdown();
            }
        }
    }
    

    下面通过修改任务数 taskNum 的值来查看执行结果

    • taskNum=2时

      当前任务数和核心线程数一致,2个核心线程执行,阻塞队列中没有等待的任务

    在这里插入图片描述

    • taskNum=5时

      当前任务数超过了核心线程数,但是等待的任务数没有超过阻塞队列的长度,这时两个线程执行,3个任务在队列中等待,最终还是由两个核心线程执行任务。

在这里插入图片描述

  • taskNum=8时

    当前任务数超过了核心线程数和等待队列的长度的和,这时线程池会再创建3个线程,此时线程数量到达最大线程数5。这时5个线程都执行。

![在这里插入图片描述](https://img-blog.csdnimg.cn/b72ca842fc1a418886b43995c7a96587.png#pic_center)
  • taskNum>8时

    当前的任务数已经超过了线程池所能容纳的最大并发任务数(最大线程数+等待队列长度),按照线程池创建时指定的拒绝策略,任务只执行到8,后面抛出异常。

在这里插入图片描述

四种拒绝策略

  1. new ThreadPoolExecutor.AbortPolicy()

    抛出异常

  2. new ThreadPoolExecutor.CallerRunsPolicy()

    由添加线程执行,task9被main线程执行

在这里插入图片描述

  1. new ThreadPoolExecutor.DiscardPolicy()

    丢掉任务,不抛出异常,任务只执行到8

在这里插入图片描述

  1. new ThreadPoolExecutor.DiscardOldestPolicy()

    尝试竞争,不抛出异常

线程池最大值设置问题

分两种情况

  1. CPU密集型

    这类任务的一个特点是非常占用cpu。所以在设置最大线程数时可以设置为CPU的核心数,这样即可以保证效率,又不会浪费资源。

    如果设置低了,并发处理任务的效率低。如果设置高了,线程上下文切换的开销会影响到处理效率。

    可以使用下列语句获取cpu核心数

    Runtime.getRuntime().availableProcessors()
    
  2. IO密集型

    这里任务的特点是,cpu在处理任务的过程中需要等待IO操作,比如读写文件,网络IO等。

    线程的上下文切换已经对任务处理效率的影响很小了。

    这时可以把最大线程数设置得比cpu核心线程数大一些,比如两倍。

原文链接:https://blog.csdn.net/wu1308156206/article/details/125688787

栏目分类
最近更新