• 六、线程池(一)


    线程池

    通过建立池可以有效的利用系统资源,节约系统性能。Java 中的线程池就是一种非常好的实现,从 JDK1.5 开始 Java 提供了一个线程工厂 Executors 用来生成线程池,通过 Executors 可以方便的生成不同类型的线程池。

    线程池的优点

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

    常见的线程池

    • CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为 Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
    • SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
    • SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
    • FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
    • Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor() 和 Executors.newCachedThreadPool() 等方法的底层都是通过 ThreadPoolExecutor 实现的。

    ThreadPoolExecutor

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            // maximumPoolSize 必须大于 0,且必须大于 corePoolSize
            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;
    }
    

    参数介绍:

    • corePoolSize

      • 线程池的核心线程数。在没有设置 allowCoreThreadTimeOut 为 true 的情况下,核心线程会在线程池中一直存活,即使处于闲置状态。
      • 如果设置为 0,则表示在没有任何任务时,销毁线程池;如果大于 0,即使没有任务时也会保证线程池的线程数量等于此值。
      • 但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程,如果设置的比较大,则会浪费系统资源,所以需要根据自己的实际业务来调整此值。
    • maximumPoolSize

      • 线程池所能容纳的最大线程数。当活动线程(核心线程+非核心线程)达到这个数值后,后续任务将会根据 RejectedExecutionHandler 来进行拒绝策略处理。
      • 官方规定此值必须大于 0,也必须大于等于 corePoolSize,此值只有在任务比较多,且不能存放在任务队列时,才会用到。
    • keepAliveTime

      • 非核心线程闲置时的超时时长。超过该时长,非核心线程就会被回收。
      • 若线程池通过 allowCoreThreadTimeOut() 方法设置 allowCoreThreadTimeOut 属性为 true,则该时长同样会作用于核心线程,AsyncTask 配置的线程池就是这样设置的。
    • unit

      • keepAliveTime 时长对应的单位。
    • workQueue

      • 表示线程池执行的任务队列,当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行。
      • 是一个阻塞队列 BlockingQueue,虽然它是 Queue 的子接口,但是它的主要作用并不是容器,而是作为线程同步的工具,他有一个特征,当生产者试图向 BlockingQueue 放入(put)元素,如果队列已满,则该线程被阻塞;当消费者试图从 BlockingQueue 取出(take)元素,如果队列已空,则该线程被阻塞。
    • ThreadFactory

      • 线程的创建工厂,功能很简单,就是为线程池提供创建新线程的功能。
      • 也可以自定义一个线程工厂,通过实现 ThreadFactory 接口来完成,这样就可以自定义线程的名称或线程执行的优先级了。
      • 通常在创建线程池时不指定此参数,它会使用默认的线程创建工厂的方法来创建线程,源代码如下:
      public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue) {
          // Executors.defaultThreadFactory() 为默认的线程创建工厂
          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
               Executors.defaultThreadFactory(), defaultHandler);
      }
      public static ThreadFactory defaultThreadFactory() {
          return new DefaultThreadFactory();
      }
      // 默认的线程创建工厂,需要实现 ThreadFactory 接口
      static class DefaultThreadFactory implements ThreadFactory {
          private static final AtomicInteger poolNumber = new AtomicInteger(1);
          private final ThreadGroup group;
          private final AtomicInteger threadNumber = new AtomicInteger(1);
          private final String namePrefix;
      
          DefaultThreadFactory() {
              SecurityManager s = System.getSecurityManager();
              group = (s != null) ? s.getThreadGroup() :
                                    Thread.currentThread().getThreadGroup();
              namePrefix = "pool-" +
                            poolNumber.getAndIncrement() +
                           "-thread-";
          }
          // 创建线程
          public Thread newThread(Runnable r) {
              Thread t = new Thread(group, r,
                                    namePrefix + threadNumber.getAndIncrement(),
                                    0);
              if (t.isDaemon()) 
                  t.setDaemon(false); // 创建一个非守护线程
              if (t.getPriority() != Thread.NORM_PRIORITY)
                  t.setPriority(Thread.NORM_PRIORITY); // 线程优先级设置为默认值
              return t;
          }
      }
      
    • RejectedExecutionHandler

      • 表示指定线程池的拒绝策略,当线程池的任务已经在缓存队列 workQueue 中存储满了之后,并且不能创建新的线程来执行此任务时,就会用到此拒绝策略.
      • 它属于一种限流保护的机制,这里有四种任务拒绝类型:
        1. AbortPolicy: 不执行新任务,直接抛出异常,提示线程池已满,涉及到该异常的任务也不会被执行,线程池默认的拒绝策略就是该策略。
        2. DisCardPolicy: 不执行新任务,也不抛出异常,即忽略此任务;
        3. DisCardOldSetPolicy: 将消息队列中的第一个任务(即等待时间最久的任务)替换为当前新进来的任务执行,忽略最早的任务(最先加入队列的任务);
        4. CallerRunsPolicy: 把任务交给当前线程来执行;
      /**
       * 线程池的拒绝策略
       */
      @Test
      public void test1() {
          // 创建线程池 核心线程为1,最大线程为3,任务队列大小为2
          ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 3, 10,
                  TimeUnit.SECONDS,
                  new LinkedBlockingDeque<>(2),
                  new ThreadPoolExecutor.AbortPolicy() // 添加 AbortPolicy 拒绝策略
          );
      
      
          for (int i = 0; i < 6; i++) {
              poolExecutor.execute(() -> {
                  System.out.println(Thread.currentThread().getName());
              });
          }
          
      }
      
      • 自定义线程池拒绝策略
      /**
       * 自定义线程池的拒绝策略
       * 实现接口 RejectedExecutionHandler
       */
      @Test
      public void test2() {
          ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10,
                  TimeUnit.SECONDS,
                  new LinkedBlockingDeque<>(2),
                  new RejectedExecutionHandler() {
      
                      @Override
                      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                          // 业务处理方法
                          System.out.println("执行自定义拒绝策略");
                      }
                  }
          );
      
          for (int i = 0; i < 6; i++) {
              executor.execute(() -> {
                  System.out.println(Thread.currentThread().getName());
              });
          }
      
      }
      

    线程池工作原理

    线程池工作原理

    线程池的工作流程要从它的执行方法 execute() 说起,源码如下:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 当前工作的线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 创建新的线程执行此任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 检查线程池是否处于运行状态,如果是则把任务添加到队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再出检查线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
            // 如果是非运行状态,则将刚加入队列的任务移除
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false); // 新建线程执行任务
        }
        // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
        else if (!addWorker(command, false)) 
            // 执行拒绝策略
            reject(command);
    }
    

    execute() VS submit()

    • execute() 和 submit() 都是用来执行线程池任务的,它们最主要的区别是,submit() 方法可以接收线程池执行的返回值,而 execute() 不能接收返回值。
    • sumbit 之所以可以接收返回值,是因为参数中可以传递:Callable task,而通过 callable 创建的线程任务有返回值并且可以抛出异常。
    /**
     * execute VS sumbin
     * execute 提交任务没有返回值
     * submit 提交任务有返回值
     */
    @Test
    public void test3() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(20));
        // execute
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, execute");
            }
        });
    
        // submit 使用
        Future<String> future = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("Hello, submit");
                return "submit success";
            }
        });
        System.out.println(future.get());
    }
    
    • 它们的另一个区别是 execute() 方法属于 Executor 接口的方法,而 submit() 方法则是属于 ExecutorService 接口的方法。

    线程池的使用:

    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author xiandongxie
     */
    public class ThreadPool {
    
        //参数初始化 返回Java虚拟机可用的处理器数量
    //    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        private static final int CPU_COUNT = 2;
        //核心线程数量大小
        private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));
        //线程池最大容纳线程数
        private static final int maximumPoolSize = CPU_COUNT * 2 + 1;
        //线程空闲后的存活时长
        private static final int keepAliveTime = 30;
    
        //任务过多后,存储任务的一个阻塞队列
        BlockingQueue<Runnable> workQueue = new SynchronousQueue<>();
    
        //线程的创建工厂
        ThreadFactory threadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "AdvacnedAsyncTask #" + mCount.getAndIncrement());
            }
        };
    
        //线程池任务满载后采取的任务拒绝策略: 不执行新任务,直接抛出异常,提示线程池已满
        RejectedExecutionHandler rejectHandler = new ThreadPoolExecutor.AbortPolicy();
    
        //线程池对象,创建线程
        ThreadPoolExecutor mExecute = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                workQueue,
                threadFactory,
                rejectHandler
        );
    
        public static void main(String[] args) {
            System.out.println("main start ..... 
    CPU_COUNT = " + CPU_COUNT + "	corePoolSize=" + corePoolSize + "	maximumPoolSize=" + maximumPoolSize);
            
            ThreadPool threadPool = new ThreadPool();
            ThreadPoolExecutor execute = threadPool.mExecute;
            // 预启动所有核心线程
            execute.prestartAllCoreThreads();
    
            for (int i = 0; i < 5; i++) {
                execute.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "	start..." + System.currentTimeMillis());
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "	end..." + System.currentTimeMillis());
                    }
                });
            }
            execute.shutdown();
            
            System.out.println("main end .....");
        }
    }
    
  • 相关阅读:
    Windows各种计时器
    C++:数据流和缓冲区
    CImage类的使用介绍!
    PCL:PCL可视化显示点云
    Qt:&OpenCV—Q图像处理基本操作(Code)
    Boost锁~临界区保护和临界资源共享
    关于XML学习
    Eigen库对齐问题:declspec(align('16')) 的形参将不被对齐
    boost多线程使用简例
    一个openMP编程处理图像的示例
  • 原文地址:https://www.cnblogs.com/xiexiandong/p/13160464.html
Copyright © 2020-2023  润新知