一、Java并发 Java ExecutorService

ExecutorService 是 Java java.util.concurrent 包的重要组成部分,是 Java JDK 提供的框架,用于简化异步模式下任务的执行。

一般来说,ExecutorService 会自动提供一个线程池和相关 API,用于为其分配任务。

一、实例化 ExecutorService

实例化ExecutorService 的方式有两种:一种是工厂方法,另一种是直接创建。

Executors.newFixedThreadPool() 工厂方法创建 ExecutorService 实例

创建ExecutorService 实例的最简单方法是使用 Executors 类的提供的工厂方法。比如

ExecutorService executor = Executors.newFixedThreadPool(10);

当然还有其它很多工厂方法,每种工厂方法都可以创建满足特定用例的预定义 ExecutorService 实例。你所需要做的就是找到自己想要的合适的方法。这些方法都在 Oracle 的 JDK 官方文档中有列出

因为ExecutorService 是只是一个接口,因此可以使用其任何实现类的实例。Java java.util.concurrent 包已经预定义了几种实现可供我们选择,或者你也可以创建自己的实现。

例如,ThreadPoolExecutor 类实现了 ExecutorService 接口并提供了一些构造函数用于配置执行程序服务及其内部池。

ExecutorService executorService = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,   
  new LinkedBlockingQueue<Runnable>()
);

你可能会注意到,上面的代码与工厂方法 newSingleThreadExecutor() 的 源代码01 非常相似。对于大多数情况,不需要详细的手动配置。

二、、将任务分配给 ExecutorService

ExecutorService 可以执行 Runnable 和 Callable 任务。为了使本文简单易懂。我们将使用两个两个原始任务,如下面的代码所示。

Runnable runnableTask = () -> {
    try {
        TimeUnit.MILLISECONDS.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Callable<String> callableTask = () -> {
    TimeUnit.MILLISECONDS.sleep(300);
    return "Task's execution";
};

List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);

注意: 上面的代码使用了 lambda 表达式而不是匿名内部类。

创建完了任务之后,就可以使用多种方法将任务分配给 ExecutorService ,比如 execute() 方法,还有 submit()、invokeAny() 和 invokeAll() 等方法。这些方法都继承自 Executor 接口。

a、 首先来看看execute()方法;

该方法返回值为空 ( void )。因此使用该方法没有任何可能获得任务执行结果或检查任务的状态( 是正在运行 ( running ) 还是执行完毕 ( executed ) )。

executorService.execute(runnableTask);

b、 其次看看submit()方法;

submit() 方法会将一个 Callable 或 Runnable 任务提交给 ExecutorService 并返回 Future 类型的结果。

Future<String> future = executorService.submit(callableTask);

c、 然后是invokeAny()方法;

invokeAll() 方法将一组任务分配给 ExecutorService ,使每个任务执行,并以 Future 类型的对象列表的形式返回所有任务执行的结果。

List<Future<String>> futures = executorService.invokeAll(callableTasks);

在继续深入理解 ExecutorService 之前,我们必须先讲解下另外两件事:关闭 ExecutorService 和处理 Future 返回类型。

三、关闭 ExecutorService

一般情况下,ExecutorService 并不会自动关闭,即使所有任务都执行完毕,或者没有要处理的任务,也不会自动销毁 ExecutorService 。它会一直出于等待状态,等待我们给它分配新的工作。

这种机制,在某些情况下是非常有用的,比如,,如果应用程序需要处理不定期出现的任务,或者在编译时不知道这些任务的数量。

但另一方面,这也带来了副作用:即使应用程序可能已经到达它的终点,但并不会被停止,因为等待的 ExecutorService 将导致 JVM 继续运行。这样,我们就需要主动关闭 ExecutorService。

要正确的关闭 ExecutorService,可以调用实例的 shutdown() 或 shutdownNow() 方法。

shutdown()方法:

executorService.shutdown();

shutdown() 方法并不会立即销毁 ExecutorService 实例,而是首先让 ExecutorService 停止接受新任务,并在所有正在运行的线程完成当前工作后关闭。

 shutdownNow()方法:

List<Runnable> notExecutedTasks = executorService.shutDownNow();

shutdownNow() 方法会尝试立即销毁 ExecutorService 实例,所以并不能保证所有正在运行的线程将同时停止。该方法会返回等待处理的任务列表,由开发人员自行决定如何处理这些任务。

因为提供了两个方法,因此关闭 ExecutorService 实例的最佳实战 ( 也是 Oracle 所推荐的 )就是同时使用这两种方法并结合 awaitTermination() 方法。

使用这种方式,ExecutorService 首先停止执行新任务,等待指定的时间段完成所有任务。如果该时间到期,则立即停止执行。

executorService.shutdown();
try {
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
        executorService.shutdownNow();
    } 
} catch (InterruptedException e) {
    executorService.shutdownNow();
}

四、Future 接口

之前提到将任务提交给executor执行框架的时候还有另外两个方法返回了Future 下面对这个返回的对象进行解释说明;

submit() 方法和 invokeAll() 方法返回一个 Future 接口的对象或 Future 类型的对象集合。这些 Future 接口的对象允许我们获取任务执行的结果或检查任务的状态 ( 是正在运行还是执行完毕 )。

Future 接口 get() 方法

Future 接口提供了一个特殊的阻塞方法 get(),它返回 Callable 任务执行的实际结果,但如果是 Runnable 任务,则只会返回 null。

因为get() 方法是阻塞的。如果调用 get() 方法时任务仍在运行,那么调用将会一直被执阻塞,直到任务正确执行完毕并且结果可用时才返回。

而且更重要的是,正在被执行的任务随时都可能抛出异常或中断执行。因此我们要将 get() 调用放在 try catch 语句块中,并捕捉 InterruptedException 或 ExecutionException 异常。如下所示:

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
    result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

因为get() 方法是阻塞的,而且并不知道要阻塞多长时间。因此可能导致应用程序的性能降低。如果结果数据并不重要,那么我们可以使用超时机制来避免长时间阻塞。

String result = future.get(200, TimeUnit.MILLISECONDS);

这个get() 的重载,第一个参数为超时的时间,第二个参数为时间的单位。上面的实例所表示就的就是等待 200 毫秒。

注意,这个 get() 重载方法,如果在超时时间内正常结束,那么返回的是 Future 类型的结果,如果超时了还没结束,那么将抛出 TimeoutException 异常。

除了get() 方法之外,Future 还提供了其它很多方法,我们将几个重要的方法罗列在此

方法说明
isDone()检查已分配的任务是否已处理
cancel()取消任务执行
isCancelled()检查任务是否已取消

使用方式如下:

boolean isDone      = future.isDone();
boolean canceled    = future.cancel(true);
boolean isCancelled = future.isCancelled();

五、 ScheduledExecutorService 接口

ScheduledExecutorService 接口用于在一些预定义的延迟之后运行任务和( 或 )定期运行任务。

同样的,实例化 ScheduledExecutorService 的最佳方式是使用 Executors 类的工厂方法(Executors 类为很多类都提供了工厂方法)。

为了简单起见,我们只使用只有一个线程的 ScheduledExecutorService 实例

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

有了实例之后,事情就好办了,比如,要在固定延迟后安排单个任务的执行,可以使用 ScheduledExecutorService 实例的 scheduled() 方法

Future<String> resultFuture = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

上面这个实例中的代码在执行 callableTask 之前延迟了一秒钟。

scheduled() 方法有两个重载,分别用于执行 Runnable 任务或 Callable 任务。

另外,ScheduledExecutorService 实例还提供了另一个重要方法 scheduleAtFixedRate() ,它允许在固定延迟后定期执行任务。

Future<String> resultFuture = service.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

上面的代码块将在 100 毫秒的初始延迟后执行任务,之后,它将每 450 毫秒执行相同的任务。

如果处理器需要更多时间来执行分配的任务,那么可以使用 scheduleAtFixedRate() 方法的 period 参数,ScheduledExecutorService 将等到当前任务完成后再开始下一个任务。

如果任务迭代之间必须具有固定长度的延迟,那么可以使用 scheduleWithFixedDelay() 方法 。例如,以下代码将保证当前执行结束与另一个执行结束之间的暂停时间为 150 毫秒。

service.scheduleWithFixedDelay(task, 100, 150, TimeUnit.MILLISECONDS);

根据scheduleAtFixedRate() 和 scheduleWithFixedDelay() 方法契约,在任务执行期间,如果 ExecutorService 终止了或任务抛出了异常,那么任务将自动结束。

总结:

尽管ExecutorService 相对简单,但仍有一些常见的陷阱。我们罗列于此

1、 保持未使用的ExecutorService存活;

本文中对如何关闭 ExecutorService 已经做出了详细解释。
2、 使用固定长度的线程池时设置了错误的线程池容量;

使用ExecutorService 最重要的一件事,就是确定应用程序有效执行任务所需的线程数

  • 太大的线程池只会产生不必要的开销,只会创建大多数处于等待模式的线程。
  • 太少的线程池会让应用程序看起来没有响应,因为队列中的任务等待时间很长。

3、 在取消任务后调用Future的get()方法;

尝试获取已取消任务的结果将触发 CancellationException 异常。
4、 使用Future的get()方法意外地阻塞了很长时间;

应该使用超时来避免意外的等待。

<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内容。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`容错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值