• 并发编程(贰):线程池浅析


    线程池浅析


    描述

    线程的创建、启动、销毁等是一个非常消耗资源的过程。引出线程池。

    线程池作用

    1. 降低资源消耗,重复利用已创建好的线程。
    2. 提高响应速度,通过已经创建好的线程直接执行到达的任务,无需等待。
    3. 线程的统一管理,对线程统一分配、监控和调优(专人专职)。

    线程池的创建一

    一、创建

    目前线程池的共有六种创建方式。先来说明常见的四种创建方式,本质都是通过改变构造函数的参数来创建不同的线程的。最终调用的构造函数都是:

        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.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    

    以下以demo分别说明线程池的创建:

    1、创建缓存线程
     ExecutorService threadPool = Executors.newCachedThreadPool();
     for(int i=0;i<10;i++){
         int temp = i;
         threadPool.execute(new Runnable() {
             @Override
             public void run() {
                 System.out.println(Thread.currentThread().getName()+","+temp);
             }
         });
     }
     threadPool.shutdown();// 停掉线程池
    
    2、创建固定长度的线程(常用)
      ExecutorService threadPool = Executors.newFixedThreadPool(3);
      for (int i = 0; i < 10; i++) {
          int temp = i;
          threadPool.execute(new Runnable() {
              @Override
              public void run() {
                  System.out.println(Thread.currentThread().getName()+","+temp);
              }
          });
      }
      threadPool.shutdown();// 停掉线程池
    
    3、创建定时线程
     ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
     threadPool.schedule(new Runnable() {
         @Override
         public void run() {
             System.out.println("我是定时线程,三秒后启动");
         }
     },3, TimeUnit.SECONDS); // 第一个参数是任务,第二个参数是时间长度,第三个参数时间单位
     threadPool.shutdown();// 停掉线程池
    
    4、创建单线程
     ExecutorService threadPool = Executors.newSingleThreadExecutor();
     for (int i = 0; i < 10; i++) {
         threadPool.execute(new Runnable() {
             @Override
             public void run() {
                 System.out.println("我是单线程线程");
             }
         });
     }
     threadPool.shutdown();// 停掉线程池
    
    二、线程池参数说明

    重点:几乎是面试必问的。

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

    corePoolSize:核心线程数

    maximumPoolSize:最大线程数

    keepAliveTimea:线程空闲时间

    unit:TimeUnit枚举类型的值,代表keepAliveTime时间单位,可以取下列值:

    • TimeUnit.DAYS; //天   
    • TimeUnit.HOURS; //小时   
    • TimeUnit.MINUTES; //分钟   
    • TimeUnit.SECONDS; //秒   
    • TimeUnit.MILLISECONDS; //毫秒   
    • TimeUnit.MICROSECONDS; //微妙   
    • TimeUnit.NANOSECONDS; //纳秒

    workQueue:阻塞队列,用来存储等待执行的任务,决定了线程池的排队策略,有以下取值:

    • ArrayBlockingQueue
    • LinkedBlockingQueue
    • SynchronousQueue

    threadFactory:线程工厂,是用来创建线程的

    handler:线程拒绝策略。当创建的线程超出maximumPoolSize,且缓冲队列已满时,新任务会拒绝,有以下取值:

    • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。   
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)   
    • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
    三、线程的创建过程

    下图是线程池的执行流程:

    流程说明:

    1. 用户提交任务,先到核心线程池,判断核心线程池是都已满;

    2. 如何核心线程池未满,线程任务执行;如何核心线程已满,走下一步;

    3. 进入线程缓存队列,判断缓存队列是否已满;

    4. 如果线程缓存队列已满,进入最大线程池;

    5. 如果最大线程池未满,创建线程任务;

    6. 如果最大线程池已满,则拒绝。

    线程池的创建二

    5、newWorkStealingPool
    ExecutorService m = Executors.newWorkStealingPool(2);
    

    1)创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传如并行级别参数,将默认为当前系统的CPU个数。

    2)每个线程维护自己的一个队列,任务执行结束了,就自己主动去取别的任务。

    3)产生的都是守护线程。

    4)不是 ThreadPoolExecutor 的扩展,是 ForkJoinPool 的扩展。

    5)演示:

    public class Main {
        public static void main(String[] args) throws Exception {
            // 设置并行级别为2,即默认每时每刻只有2个线程同时执行
            ExecutorService m = Executors.newWorkStealingPool(2);
            for (int i = 1; i <= 10; i++) {
                final int count=i;
                m.submit(new Runnable() {
                    @Override
                    public void run() {
                        Date now=new Date();
                        System.out.println("线程" + Thread.currentThread() + "完成任务:"+ count+"   时间为:"+    now.getSeconds());
                        try {
                            Thread.sleep(1000);//此任务耗时1s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }); 
            }
            while(true){
                //主线程陷入死循环,来观察结果,否则是看不到结果的
            }
        }
    }
    
    6、ForkJoinPool

    该线程池的思想是,把运用递归的思想,将大的任务拆分成小的任务(可以根据业务需求来控制拆分的粒度)。下面以一个面试题演示:一个长度100万的数组,元素是一百以内的随机数,将各个元素相加。

    public class ForkJoin {
        static int[] nums = new int[1000000];
        static final int MAX_NUM = 50000;
        private static Random random = new Random();
        static {
            for (int i = 0; i < nums.length; i++) {
                nums[i] = random.nextInt(100);
            }
            System.out.println(Arrays.stream(nums).sum()); // 传统的方式计算
        }
        // 递归思想,不断将大任务分成小任务。
        // RecursiveAction 无返回值;RecursiveTask 有返回值。
        static class AddTask extends RecursiveAction{
            int start,end;
            AddTask(int start, int end) {
                this.start = start;
                this.end = end;
            }
            @Override
            protected void compute() {
                if(end-start<=MAX_NUM){
                    long sum = 0L;
                    for (int i = start; i < end; i++) {
                        sum += nums[i];
                    }
                    System.out.println("from+"+start+"to"+end+"="+sum);
                }else{
                    int middle =start + (end-start)/2;
                    AddTask addTask = new AddTask(start,middle);
                    AddTask addTask2 = new AddTask(middle,end);
                    addTask.fork();
                    addTask2.fork();
                }
            }
        }
        public static void main(String[] args) throws IOException {
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            AddTask addTask = new AddTask(0,nums.length);
            forkJoinPool.execute(addTask);
            System.in.read();
        }
    }
    

    该线程池的优势是,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。

  • 相关阅读:
    通过C#的HttpClient模拟form表单请求
    通过Mysql连接ASP.Net Core2.0(Code First模式)
    每天一点产品思考(1):微博是否可以尝试取消点赞数展示?
    Google Analytics 学习笔记四 —— GA的Channels划分规则
    Google Analytics 学习笔记三 —— GA常用术语
    Google Analytics 学习笔记二 —— GA部署
    Google Analytics 学习笔记一 —— GA简介
    java基础知识总结(一)
    java源码解析之String类(二)
    java源码解析之String类(一)
  • 原文地址:https://www.cnblogs.com/xzy-/p/10928978.html
Copyright © 2020-2023  润新知