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

原文地址:https://www.cnblogs.com/wangshen31/p/10451193.html

时间: 2024-11-09 09:29:19

Executor等系列概念介绍的相关文章

DNS系列- 1.dns基本概念介绍

DNS系列- 1.dns基本概念介绍     目录         前言         一.概述             1.名词解释             2.DNS域名结构         二.DNS域名解析             1.查询类型             2.解析类型             3.DNS服务器的类型             4.区域传输             5.解析过程             6.解析答案         三.资源记录        

faster-rcnn系列原理介绍及概念讲解

faster-rcnn系列原理介绍及概念讲解 原文地址:https://www.cnblogs.com/inception6-lxc/p/8390729.html

Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

前言:我在最开始学习Shiro这个框架时,在网上搜索到的一个介绍比较全面的教程是:<跟我学Shiro>系列教程.但是在我看了他写的前几篇文章后,我发现虽然他在这个系列教程中把shiro的一些特性介绍地非常全面详细,但是整个教程的叙述方式还是有很大缺陷的.文章与文章之间并没有很好地串联起来,每篇文章介绍的东西都过于分散了,如果是对shiro完全不了解的新手来看的话完全是一场噩梦.就像一个网友评价的这样: 看了看这个教程,看完之后都想放弃shiro了,完全看不懂,后来百度了很多别的资料才理解了sh

Eclipse插件终极攻略(一):基本概念介绍

在这个系列的第一部分里,将对Eclipse和插件的概要.插件开发的基本概念.OSGi和SWT进行简单介绍. 1.Eclipse的架构 Eclipse被作为java的IDE(集成开发环境)被广泛的应用,但是从本质上看Eclipse是一个整合了各种开发工具的平台.因此,它采用了可以自由的增加各种功能的插件架构技术.Eclipse平台的基本架构如图1-1所示. 图1-1 Eclipse的架构 在这里,在最底层位置的是作为Eclipse插件架构基干的OSGi运行时.虽然在早期的Eclipse版本中已经

RAID 级别和概念介绍

RAID的级别和概念介绍 RAID的意思是廉价磁盘冗余阵列,但现在它被称为独立磁盘冗余阵列.本文介绍什么是RAID和在实际环境大多采用哪个级别的RAID. RAID 的意思是廉价磁盘冗余阵列(Redundant Array of Inexpensive Disks),但现在它被称为独立磁盘冗余阵列(Redundant Array of Independent Drives).早先一个容量很小的磁盘都是非常昂贵的,但是现在我们可以很便宜的买到一个更大的磁盘.Raid 是一系列放在一起,成为一个逻辑

《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void execute(Runnable command); } ExecutorService接口扩展了Executor接口,增加状态控制,执行多个任务返回Future. 关于状态控制的方法: // 发出关闭信号,不会等到现有任务执行完成再返回,但是现有任务还是会继续执行, // 可以调用awaitTe

大白话5分钟带你走进人工智能-第二十八节集成学习之随机森林概念介绍(1)

                                                      第二十八节集成学习之随机森林概念介绍(1) 从本系列开始,我们讲解一个新的算法系列集成学习.集成学习其实是怎么样去应用决策树解决一些问题. 在机器学习领域集成学习是一种非常简单直接的提升分类器回归器预测效果的一种思路.决策树有一个困境,当层数太深的时候会有过拟合问题,当我不想过拟合,就通过预剪枝给它砍掉一部分深度,此时损失又容易太大了,导致在训练集上预测的又不怎么准.所以对于决策树很难去找

输入子系统概念介绍

输入子系统在内核中的位置:/driver/input drivers/input/input.c: input_init ---> err = register_chrdev(INPUT_MAJOR, "input", &input_fops); static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 问:怎么读按键? inpu

足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一)

在足球赛事数据库以及统计分析预测平台中,有很多概念,如果不搞懂,很难进行下一步的工作.所以为了配合团队人员的学习和任务进行,特意编写这篇文章.如果有其他问题和不懂的,请留言,将根据情况进行更新. 本文原文地址:足彩基础知识入门(4)赛事数据库与预测平台基础概念介绍(一) 1.指数1/2/3.... 我在 足彩基础知识入门(3)足彩赔率的本质 一文中介绍了赔率的概念,那么指数的概念和赔率以及结果是相关的.我们举个例子: 如上图的比赛,前面是竞彩非让球的赔率:1.74-3.25-4.15,也就是说