zoukankan      html  css  js  c++  java
  • Executor等系列概念介绍

    这里对几个常见的的名词进行介绍

    Executor

    这是个接口,只声明了一个方法——

    public interface Executor {
    
        void execute(Runnable command);---执行一个Runnable对象
    
    } 

    Executors

    然后是Executors类,这个可以看作是个公共类,它提供了许多强大有用的获取线程池的static方法:

    1.public static ExecutorService newFixedThreadPool(int nThreads) 

    创建固定数目线程的线程池。

    2.public static ExecutorService newCachedThreadPool() 

    创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

    3.public static ExecutorService newSingleThreadExecutor() 

    创建一个单线程化的Executor。

    4.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 

    创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

    ExecutorService

    这也是个接口,继承了Executor接口。我觉得这个可以理解成一个代表可控线程池的一个接口,是Java提供的一个线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。

    ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

    ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

    线程池实现类——ThreadPoolExecutor

    这个类可以说是Exector框架的核心所在,Executors工具类中提供的很多线程池都是产自这个ThreadPoolExecutor类的构造方法,只是不同的线程池用了不同的默认参数而已。

    public ThreadPoolExecutor(int corePoolSize, ----池子里面的线程数量,即便idle状态的线程也会保留这个数量
                                  int maximumPoolSize,----池子里面能盛放最大线程数量
                                  long keepAliveTime,----当线程数量大于core核心数量的时候,并且里有idle状态的线程,那么最大可以被terminate的的等待时间
                                     (就是在cpu借的线程资源如果闲置多久就必须还回去)
                                  TimeUnit unit,--timeUnit
                                  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;
        }

    如果你了解这个ThreadPoolExecutor类,可以直接用它来自定义自己想要的线程池,如果不熟就直接用Executors来获取呗。

    上个Executor系列框架的类图:

    (图来自:https://www.cnblogs.com/congsg2016/p/5621746.html)

    这个AbstractExecutorService就一个实现了ExecutorService接口的抽象类;

    然后这个ScheduleExecutorService是和时间、周期操作有关的线程池接口,继承了ExecutorService接口;

    这个ScheduledThreadPoolExecutor无疑是这个和时间周期相关的实现类,这个类继承了ThreadPoolExecutor类并实现了ScheduledExecutorService接口;

    这个ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。

    Callable和future

    这两个都是接口。

    Callable接口代表一段可以调用并返回结果的代码;Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

    callable一般是配合ExecutorService来使用的,callable可以类比Runnable,但它是一个可以获得结果的Runnable。当你想获得一个线程的运行结果的时候,就可以用这个接口了。

    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

    Future接口:

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

    分别介绍下这几个方法:

    cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。

    isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

    isDone方法表示任务是否已经完成,若任务完成,则返回true;

    get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

    get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

    FutureTask类

    也就是说Future提供了三种功能:

      1)判断任务是否完成;

      2)能够中断任务;

      3)能够获取任务执行结果。

    因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

    FutureTask类实现了RunnableFuture接口——

    public interface RunnableFuture<V> extends Runnable, Future<V> {  
        void run();  
    }  

    其他另外自行查询

    然后上一个例子来理解下Future-Callable机制:

    这是个用两条线程一起寻找数组最大值的例子:

    先是Callable实现类:

    import java.util.concurrent.Callable;
    class FindMaxTask implements Callable<Integer> {
        private int[] data;
        private int start;
        private int end;
    
        FindMaxTask(int[] data, int start, int end) {
            this.data = data;
            this.start = start;
            this.end = end;
        }
    
        public Integer call() {
            int max = Integer.MIN_VALUE;
            for (int i = start; i < end; i++) {
                if (data[i] > max) max = data[i];
            }
            return max;
        }
    }

    这个call方法就有点像Runnable里面的run方法,但区别是当这个Callable所代表的线程run起来后,结束后会返回一个结果,而这个结果我们待会通过Future类可以获得。

    import java.util.concurrent.*;
    public class MultithreadedMaxFinder {
        public static int max(int[] data) throws InterruptedException, ExecutionException {
            if (data.length == 1) {
                return data[0];
            } else if (data.length == 0) {
                throw new IllegalArgumentException();
            }
    
            // split the job into 2 pieces
            FindMaxTask task1 = new FindMaxTask(data, 0, data.length/2);
            FindMaxTask task2 = new FindMaxTask(data, data.length/2, data.length);
    
            // spawn 2 threads
            ExecutorService service = Executors.newFixedThreadPool(2);
            Future<Integer> future1 = service.submit(task1);
            Future<Integer> future2 = service.submit(task2);
            return Math.max(future1.get(), future2.get());
        }
    }

    这个代码下,两个子数组都是同时被搜索。

    有一点要注意一下,当future1.get()方法被调用的时候,这个方法会被阻塞了,或者说理解成这个方法所在的线程(也就是主线程)会阻塞,直到这个future1代表的callable线程完成了它的call方法并返回结果后,才会调用future2.get()方法,所以很可能future2所表示的线程提前完成也就提前找到了第二个数组的max,这样future1.get()完后立刻调用future2.get()就可以获得值。

    所以,利用ExecutorService、callable还有Future可以让我们可以创建很多不同的线程,然后按照需要的顺序得到我们想要的答案。

    参考文章:

    https://www.cnblogs.com/fengsehng/p/6048610.html——《为什么引入Executor线程池框架》讲线程池怎么用,很多例子

    https://www.cnblogs.com/congsg2016/p/5621746.html——讲Executor框架中各个类、接口的大概关系

    https://www.cnblogs.com/fengsehng/p/6048609.html——《Java程序员必须掌握的线程知识-Callable和Future》讲Callable、Future还有FutureTask

  • 相关阅读:
    NaN数值类型
    模板字符串
    一文带你速懂虚拟化KVM和XEN
    CentOS 8配置本地yum源及DNF简介
    fxksmdb.exe 是什么进程?
    入行IT,一定要会Linux吗?
    干货|Linux平台搭建网关服务器
    忘带U盘了??别急!一行python代码即可搞定文件传输
    手把手教你如何搭建一个私有云盘
    误删重要文件怎么办?学会Linux 救援模式再也不担心
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10451193.html
Copyright © 2011-2022 走看看