zoukankan      html  css  js  c++  java
  • java核心-多线程(6)-线程池-ThreadPoolExecutor

    1.java多线程编程少不了使用线程池,线程池相关的工具类所在jdk包,java.util.concurrent

    2.使用示例

    demo1

    public class ThreadPoolDemo {
        /*
        本示例使用线程池实现两个线程交替打应数字,直到10
         */
        private static Object obj1 = new Object();
        private static int num = 0;
    
        public static void main(String[] args){
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 10000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10));
            //打印奇数线程
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("奇数run");
                    while (true){
                        synchronized (obj1){
                            if(num > 10){
                                break;
                            }
                            System.out.println("奇数抢到" + num);
                            if(num/2 != 0){
                                System.out.println("奇:" + num);
                                num++;
                                obj1.notifyAll();
                            }else{
                                try {
                                    System.out.println("奇数线程等待");
                                    obj1.wait();   //wait会让出锁;
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            });
            //打印偶数线程
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("偶数run");
                    while(true){
                        synchronized (obj1){
                            if(num > 10){
                                break;
                            }
                            System.out.println("偶数抢到" + num);
                            if(num/2 == 0){
                                System.out.println("偶:" + num);
                                num++;
                                obj1.notifyAll();
                            }else{
                                try {
                                    System.out.println("偶数线程等待");
                                    obj1.wait();   //wait会让出锁;
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            });
        }
    }
    

    意外发生了,我发现我随手写的代码是垃圾,下面是运行结果

    分析一下结果

    前面是上班期间匆匆忙忙写的代码,周末回家定睛一看,代码中一个很低级的错,就是取余%符号写成了除号/,我实在很是无语,突然让我想起今日听到的一句话——“粗心大意是基本功不扎实的表现”,本想把前面的都删了,转念一想,干嘛呢?写这些不就是记录自己的学习过程的吗,这些错误会成为以后回忆这篇文字的hook,都记下来吧。
    改正后运行效果,程序实现了两个线程交替打印0和1

    3.ThreadPoolExecutor类分析

    3.1.构造方法
        前面例子中构造方法:
        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)         //拒绝策略
          解释
    1.核心线程数:当提交新任务时,线程池中线程小于核心线程数,则会创建一个线程处理该新任务。
    2.最大线程数:当提交新任务时,如果任务队列已满且线程数量小于最大线程数时,则会创建一个新线程处理该任务。
    3.线程存活时间:线程池中线程数量大于核心线程数时,空闲时间超过该设定时间的线程被销毁。
    4.时间单位:存活时间的单位
    5.任务队列:当线程数量等于核心线程设定值时,新提交的任务会被放到任务队列中。任务队列有好几种,后面再细说。
    6.线程工厂:用来创建线程,可以控制线程所属组合线程名,后面细说。
    7.拒绝策略:当提交新任务时,任务队列已满且线程达到最大线程数,此时调用拒绝策略,后面再细说。
    

    ThreadPool总结参考

    3.2.核心方法

    3.3.线程池工作过程
    <1>任务提交过程
        submit()方法最终会调用execute()执行任务。execute()方法中多次调用addWorker方法,该方法的主要作用就是创建一个线程来执行Runnable对象。
        execute()方法执行一个Runnable对象时,首先通过workerCountOf(c)获取线程池中线程的数量,如果池中的数量小于corePoolSize就调用addWorker添加一个线程来执行这个任务。否则通过workQueue.offer(command)方法入列。如果入列成功还需要在一次判断池中的线程数,因为我们创建线程池时可能要求核心线程数量为0,所以我们必须使用addWorker(null, false)来创建一个临时线程去阻塞队列中获取任务来执行。
        提交时涉及构造方法中的四个参数corePoolSize、maxPoolSize、blockingQueue、rejectHandler。具体关系很好懂,结合前面参数理解,或者想象一下现实排队策略(正编员工,队列,临时工)就明白了。
    
    <2>执行过程
        Thread的run方法实际上调用了Worker类的runWorker方法。
        Worker类是ThreadPoolExecutor类中私有类
    private final class Worker
            extends AbstractQueuedSynchronizer  implements Runnable
    
    <3>关闭过程
        涉及shutdown()、shutdownNow()、awaitTermination(long timeout, TimeUnit unit)方法
    之前自己工作遇到的一个停止demo如下:
    
            threadPoolExecutor.shutdown();  //停止提交任务,执行完当前和队列中的任务(好比开始考试了,迟到的学生停止入场)
            try {
                if(!threadPoolExecutor.awaitTermination(120, TimeUnit.MINUTES)){  //120min过后检测是否停掉,中间即便早执行完,也不会返回(好比120分钟考试结束,判断是否所有学生都交卷)
                    System.out.println("规定时间内没有执行所有任务");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                threadPoolExecutor.shutdownNow(); //最后再强行停止(好比,有些学生最后被老师强行收卷,但老师可能还干不过学生)
            }
    

    线程池关闭&监控&结构图画法帮助的参考

    3.4.类结构关系


    Executor类并不是线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService;
    ScheduledExecutorService, 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题;
    ThreadPoolExecutor,线程池的真正实现;
    ScheduledThreadPoolExecutor,周期性任务调度的类实现;

    4.Executors类和各种线程池

    要配置一个线程池是比较复杂的,尤其是对线程池的原理不是很清楚的情况下,很可能配置的线程池不是较优的,因此Executors类里面提供了一些静态工厂,生成一些常用的线程池。
    1.newCachedThreadPool: 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,新建线程(线程最大并发数不可控制)。
        使用场景:CachedThreadPool 用于并发执行“大量短期的小任务”,或者是负载较轻的服务器。
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
    2.newFixedThreadPool: 创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
        使用场景:FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。
         public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
         }
    
    3.newScheduledThreadPool: 创建一个定时线程池,支持定时及周期性任务执行。
    scheduleAtFixedRate,固定周期执行,周期到了没有分配到线程的任务就不执行了
    scheduleWithFixedDelay,固定延迟,所有任务都会执行
    execute(), 普通立马执行, 前面两种执行具体后面专门介绍一下,这里只是简单试验得出结论
    使用场景:ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
        public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,   //调用ThreadPoolExecutor的构造方法
                  new DelayedWorkQueue());
        }
    
    
    
    4.newSingleThreadPool: 创建一个单线程化得线程池,它只会用唯一的工作线程执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。
        使用场景:SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。
        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,    //keepAliveTime为0,表示空闲线程立即销毁,但此时corePoolSize=maxPoolSize,所以这个设置没有意义
                                        new LinkedBlockingQueue<Runnable>()));    //LinkedBlockingQueue队列最大容量Integer.MAX_VALUE,相当于没有上限
        }
    

    参考

    5.其他

    5.1.阿里手册对线程池使用约定
    【强制】线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。
    附加说明:Executors返回线程池对象的弊端如下
    1.FixedThreadPool和SingleThreadPool允许队列的长度Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
    2.CachedThreadPool和ScheduledThreadPool允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM。
    
    5.2 其他一些忘记总结的知识点
    1.线程池状态,通过ThreadPoolExecutor的成员变量控制
        private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING,0));
        ctl主要用于存储线程池的工作状态以及池中正在运行的线程数。显然要在一个整型变量存储两个数据,只能将其一分为二。其中高3bit用于存储线程池的状态,低位的29bit用于存储正在运行的线程数。
        五种状态:
    RUNNING,允许提交并处理任务;
    SHUTDOWN,不允许提交新任务,但会处理已提交任务;
    STOP,不允许提交新任务,也不会处理阻塞队列中未执行的任务,并设置正在执行线程的中断标志位;
    TIDYING,所有任务执行完毕,池中工作线程数量为0,等待执行terminated()钩子方法;
    TERMINATED, terminated()钩子方法执行完毕;
    
  • 相关阅读:
    unity c# 获取系统时间
    如果你想让继承MonoBehaviour的类变成Singleten
    关于程序员
    开始养成记录的习惯吧
    关于结构体的赋值问题
    数学中的集合,群,环,域
    励志
    [编程题] 进制均值
    javaEE 入门
    jsp内置对象2
  • 原文地址:https://www.cnblogs.com/leeethan/p/12126298.html
Copyright © 2011-2022 走看看