• Executor


    一、为什么需要Executor?
    为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员
    有效的进行线程控制。他们都在java.util.concurrent包中,是JDK并发包的
    核心。其中有一个比较重要的类:Executors,他扮演着线程工厂的角色,我
    们通过Executors可以创建特定功能的线程池。

    newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始
    终不变。当已有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在
    一个任务队列中等待有空闲的线程去执行。
    newSingleThreadExecutor()方法,创建一个线程的线程池,若空闲则执行,若没有
    空闲线程则暂缓在任务队列中。
    newCachedThreadPool()方法,返回一个可根据实际情况调整线程个数的线程池,不
    限制最大线程数量,若有空闲的线程则执行任务,若无任务则不创建线程。并且
    每一个空闲线程会在60秒后自动回收。
    newScheduledThreadPool()方法,该方法返回一个SchededExecutorService对象,但
    该线程池可以指定线程的数量。

    二、对上面的几个JDK提供的线程池做分析?
    四个方法底层代码都是创建了一个ThreadPoolExecutor对象返回的。
    自定义线程池
    若Executor工厂类无法满足我们的需求,可以自己去创建自定义的线程池,其实
    Executors工程类里面的创建线程方法其内部实现均是用了ThreadPoolExecutor这个
    类,这个类可以自定义线程。构造方法如下:
    public ThreadPoolExecutor(
    int corePoolSize, //核心线程数,new的时候直接初始化的线程数量
    int maxinumPoolSize, //最大线程数
    long keppAliveTime, //空闲时间
    TimeUnit unit, //时间单位
    BlockingQueue<Runnable> workQueue, //任务队列
    ThreadFactory threadFactory, //
    RejectedExecutionHandler handler //
    )

    对于newFixedThreadPool(10)
    ThreadPoolExecutor(10, 10,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());

    对于newSingleThreadExecutor()
    new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>())

    对于newCachedThreadPool()
    ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>());
    这里需要注意了,刚创建完CacheThreadPool对象,池中是没有任何线程的,
    也就是说来一个任务会创建一个线程去执行;并且如果一个线程空闲时间大于60s了
    那么他就会被释放掉了。为什么会来一个任务就创建一个线程,是因为使用了
    SynchronousQueue的原因。

    对于newScheduledThreadPool(10)
    super(10, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
    new DelayedWorkQueue());
    说明这个线程池也是不限制任务数量的,默认池初始化有10个线程,
    并且使用的是DelayedWorkQueue()说明是带有定时任务的一些任务。

    三、newScheduledThreadPool的使用
    class Temp extends Thread{
    @Override
    public void run() {
    System.out.println("temp run...");
    }
    }

    public class TestFixedThreadPool {
    public static void main(String[] args) {
    /**
    ExecutorService pool1 = Executors.newFixedThreadPool(10);
    ExecutorService pool2 = Executors.newSingleThreadExecutor();
    ExecutorService pool3 = Executors.newCachedThreadPool();
    **/
    ScheduledExecutorService pool4 = Executors.newScheduledThreadPool(10);
    //其中的5是初始化时间
    //其中的1是轮询时间
    //下面这句代码的执行结果是,程序启动后延迟5s的时间来初始化任务,然后每隔一秒钟就执行一次run方法
    pool4.scheduleWithFixedDelay(new Temp(), 5, 1,TimeUnit.SECONDS);
    }
    }

    四、自定义线程池
    public ThreadPoolExecutor(
    int corePoolSize, //核心线程数,new的时候直接初始化的线程数量
    int maxinumPoolSize, //最大线程数
    long keppAliveTime, //空闲时间
    TimeUnit unit, //时间单位
    BlockingQueue<Runnable> workQueue, //任务队列
    ThreadFactory threadFactory, //
    RejectedExecutionHandler handler //
    )
    这个构造方法对于队列是什么类型的比较关键
    在使用有界队列时,若有新的任务需要执行,如果线程池实际线程小于corePoolSize,
    则优先创建线程;若大于corePoolSize,则会将任务加入队列,若队列已满,则在总
    线程数不大于maxnumPoolSize的前提下,创建新的线程,若线程数大于maxmunPoolSize,
    则执行拒绝策略。或其他自定义方式。
    无界的任务队列时:LinkedBlockingQueue。与有界队列相比,除非系统资源耗尽,否则
    无界的任务队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于
    corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。
    若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待。若任务
    创建和处理的速度差异很大,无界队列会保持快速增长,直至耗尽系统内存。
    JDK拒绝策略:
    AbortPolicy:直接抛出异常组织系统正常工作
    CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
    DiscardOldestPolicy:丢弃最老的一个请求,尝试再次提交当前任务。
    DiscardPolocy:丢弃无法处理的任务,不给于任何处理。
    如果需要自定义拒绝策略可以实现RejectExecutionHandler接口。

    4.1有界队列的例子
    public class UseSizeQueue {
    public static void main(String[] args) {
    /**
    * 在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
    * 若大于corePoolSize,则会将任务加入队列, 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
    * 若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。
    *
    */
    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, // coreSize
    2, // MaxSize
    60, // 60
    TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3) // 指定一种队列(有界队列)
    , new MyRejected()
    );

    MyTask mt1 = new MyTask(1, "任务1");
    MyTask mt2 = new MyTask(2, "任务2");
    MyTask mt3 = new MyTask(3, "任务3");
    MyTask mt4 = new MyTask(4, "任务4");
    MyTask mt5 = new MyTask(5, "任务5");
    MyTask mt6 = new MyTask(6, "任务6");

    pool.execute(mt1);
    pool.execute(mt2);
    pool.execute(mt3);
    pool.execute(mt4);
    pool.execute(mt5);
    pool.execute(mt6);

    //调用线程池的shutdown方法,并不是直接这个线程池就销毁了
    //而是等到所有任务运行结束
    pool.shutdown();
    }
    }
    1.如果上述代码只有pool.execute(mt1);的话,那么输出是:run taskId =1,并且在5s之后程序停止,这就是第一种情况:
    在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程。
    2.如果上述代码有
    pool.execute(mt1);
    pool.execute(mt2);
    两行,那么输出是:
    run taskId =1
    run taskId =2
    并且先输出run taskId=1等待5s之后输出run taskId =2,再等待5s之后程序停止,这就是第二种情况:
    若大于corePoolSize,则会将任务加入队列,
    3.如果上述代码有5行
    pool.execute(mt1);
    pool.execute(mt2);
    pool.execute(mt3);
    pool.execute(mt4);
    pool.execute(mt5);
    则输出是这样的:
    run taskId =1
    run taskId =5
    run taskId =2
    run taskId =3
    run taskId =4
    首先是1和5执行,然后经过5s后,2和3执行,在经过5s后4执行,然后经过5s后程序结束。
    这就是第三种情况:
    若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
    4.如果代码有6行:
    pool.execute(mt1);
    pool.execute(mt2);
    pool.execute(mt3);
    pool.execute(mt4);
    pool.execute(mt5);
    pool.execute(mt6);
    执行效果是:
    自定义处理..
    run taskId =1
    当前被拒绝任务为:6
    run taskId =5
    run taskId =2
    run taskId =3
    run taskId =4
    也就是说,当6来的时候被拒绝了,这就是第四种情况:
    若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。

    4.2无界队列的例子
    public class NoSizeQueue implements Runnable{

    private static AtomicInteger count = new AtomicInteger(0);

    public void run() {
    try {
    int temp = count.incrementAndGet();
    System.out.println("任务" + temp);
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    public static void main(String[] args) throws Exception{
    BlockingQueue<Runnable> queue =
    new LinkedBlockingQueue<Runnable>();
    ExecutorService executor = new ThreadPoolExecutor(
    5, //core
    10, //max
    120L, //2fenzhong
    TimeUnit.SECONDS,
    queue);

    for(int i = 0 ; i < 20; i++){
    executor.execute(new NoSizeQueue());
    }
    Thread.sleep(1000);
    System.out.println("queue size:" + queue.size()); //10
    Thread.sleep(2000);
    }

    }
    执行的结果是:
    任务1
    任务2
    任务3
    任务5
    任务4
    queue size:15
    任务6
    任务7
    任务8
    任务9
    任务10
    任务11
    任务12
    任务13
    任务14
    任务15
    任务16
    任务17
    任务18
    任务19
    任务20
    先执行了前5个任务,然后休息了时间。然后又取出了5个任务,然后又取出了5个任务,最后又取出了5个任务
    最先前的5个任务到位后,因为当前线程数小于coreSize,所以就直接新建线程执行了。
    其他的后面的15个任务会加入到队列中,等待被执行。

    五、拒绝策略
    AbortPolicy:直接抛出异常组织系统正常工作,当前任务丢失了,但是队列中等到的任务继续执行。
    CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
    如果需要自定义拒绝策略可以实现RejectExecutionHandler接口。
    public class MyRejected implements RejectedExecutionHandler{


    public MyRejected(){
    }
    //其中r就是线程对象MyTask的一个实例
    //executor就是当前的线程池对象
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    System.out.println("自定义处理..");
    System.out.println("当前被拒绝任务为:" + r.toString());

    }

    }
    常用的拒绝策略是:写日志,然后写一个定时的job,读取log日志,然后重跑。
    或者向数据源发送拒绝消息

  • 相关阅读:
    微软外服 AlI In One
    js 循环多次和循环一次的时间的性能对比 All In One
    vue inject All In One
    Excel 表格数据倒置 All In One
    SVG tickets All In One
    OH MY ZSH All In One
    js array for loop performance compare All In One
    mac terminal show You have new mail All In one
    新闻视频 26 制作母版页
    转自牛腩 母版页和相对路径
  • 原文地址:https://www.cnblogs.com/dongdone/p/5750272.html
Copyright © 2020-2023  润新知