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

线程池浅析


描述

线程的建立、启动、销毁等是一个很是消耗资源的过程。引出线程池。java

线程池做用

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

线程池的建立一

1、建立

目前线程池的共有六种建立方式。先来讲明常见的四种建立方式,本质都是经过改变构造函数的参数来建立不一样的线程的。最终调用的构造函数都是:面试

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分别说明线程池的建立:数组

一、建立缓存线程
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();// 停掉线程池
二、建立固定长度的线程(经常使用)
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();// 停掉线程池
三、建立定时线程
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
 threadPool.schedule(new Runnable() {
     @Override
     public void run() {
         System.out.println("我是定时线程,三秒后启动");
     }
 },3, TimeUnit.SECONDS); // 第一个参数是任务,第二个参数是时间长度,第三个参数时间单位
 threadPool.shutdown();// 停掉线程池
四、建立单线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
 for (int i = 0; i < 10; i++) {
     threadPool.execute(new Runnable() {
         @Override
         public void run() {
             System.out.println("我是单线程线程");
         }
     });
 }
 threadPool.shutdown();// 停掉线程池
2、线程池参数说明

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

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

corePoolSize:核心线程数dom

maximumPoolSize:最大线程数ide

keepAliveTimea:线程空闲时间函数

unit:TimeUnit枚举类型的值,表明keepAliveTime时间单位,能够取下列值:this

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

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

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • SynchronousQueue

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

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

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

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


流程说明:

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

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

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

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

  5. 若是最大线程池未满,建立线程任务;

  6. 若是最大线程池已满,则拒绝。

线程池的建立二

五、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){
            //主线程陷入死循环,来观察结果,不然是看不到结果的
        }
    }
}
六、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的优点,把一个任务拆分红多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成以后,再将这些执行结果合并起来便可。

相关文章
相关标签/搜索