【线程 锁】ExecutorService的submit方法使用

本文介绍了Java 5后ExecutorService的使用,包括创建任务、执行任务、关闭服务、获取返回值,重点讲解了execute和submit方法的区别,以及如何利用submit进行异常处理和异步任务管理。通过实例演示了如何处理Callable任务的返回结果和错误处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动、调度、管理线程的一大堆API了。在Java5以后,通过Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。

一、创建任务

任务就是一个实现了Runnable接口的类。
创建的时候实run方法即可。

二、执行任务

通过java.util.concurrent.ExecutorService接口对象来执行任务该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建

Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
executorService.execute(new TestRunnable());

1、创建ExecutorService

通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

比如,创建一个ExecutorService的实例,ExecutorService实际上是一个线程池的管理工具:

        ExecutorService executorService = Executors.newCachedThreadPool();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        ExecutorService executorService = Executors.newSingleThreadExecutor();

2、将任务添加到线程去执行

当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。

三、关闭执行服务对象

executorService.shutdown();

四、获取任务的执行的返回值

在Java5之后,任务分两类:

  • 一类是实现了Runnable接口的类
  • 一类是实现了Callable接口的类。

在这里插入图片描述

两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的( task) 方法来执行,并且返回一个 ,是表示任务等待完成的 Future。

public interface Callable<V>
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

类包含一些从其他普通形式转换成 Callable 类的实用方法。

Callable中的call()方法类似Runnable的run()方法,就是前者有返回值,后者没有:

  • 当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。

  • 同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null

五、ExecutorService的execute和submit方法区别,以及submit使用的具体Demo

1、接收的参数不一样

2、submit有返回值,而execute没有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。

个人觉得cancel execution这个用处不大,很少有需要去取消执行的。

而最大的用处应该是第二点。

3、submit方便Exception处理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will Go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

比如说,我有很多更新各种数据的task,我希望如果其中一个task失败,其它的task就不需要执行了。那我就需要catch Future.get抛出的异常,然后终止其它task的执行,代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> resultList = new ArrayList<Future<String>>();

        // 创建10个任务并执行
        for (int i = 0; i < 10; i++) {
            // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<String> future = executorService.submit(new TaskWithResult(i));
            // 将任务执行结果存储到List中
            resultList.add(future);
        }
        executorService.shutdown();

        // 遍历任务的结果
        for (Future<String> fs : resultList) {
            try {
                System.out.println(fs.get()); // 打印各个线程(任务)执行的结果
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                executorService.shutdownNow();
                e.printStackTrace();
                return;
            }
        }
    }
}

class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    /**
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行。
     *
     * @return
     * @throws Exception
     */
    public String call() throws Exception {
        System.out.println("call()方法被自动调用,干活!!!             " + Thread.currentThread().getName());
        // 下面的判读是模拟一个抛出异常的操作,随机得到一个true
        if (new Random().nextBoolean())
            throw new TaskException("Meet error in task." + Thread.currentThread().getName());
        // 一个模拟耗时的操作
        for (int i = 999999999; i > 0; i--)
            ;
        return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName();
    }
}

class TaskException extends Exception {
    public TaskException(String message) {
        super(message);
    }
}

一种可能的执行结果:

call()方法被自动调用,干活!!!             pool-1-thread-4
call()方法被自动调用,干活!!!             pool-1-thread-2
call()方法被自动调用,干活!!!             pool-1-thread-1
call()方法被自动调用,干活!!!             pool-1-thread-5
call()方法被自动调用,干活!!!             pool-1-thread-3
call()方法被自动调用,干活!!!             pool-1-thread-7
call()方法被自动调用,干活!!!             pool-1-thread-8
call()方法被自动调用,干活!!!             pool-1-thread-4
call()方法被自动调用,干活!!!             pool-1-thread-6
call()方法被自动调用,干活!!!             pool-1-thread-9
call()方法被自动调用,任务的结果是:0    pool-1-thread-1
call()方法被自动调用,任务的结果是:1    pool-1-thread-2
call()方法被自动调用,任务的结果是:2    pool-1-thread-3
java.util.concurrent.ExecutionException: TaskException: Meet error in task.pool-1-thread-4
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at ExecutorServiceTest.main(ExecutorServiceTest.java:27)
Caused by: TaskException: Meet error in task.pool-1-thread-4
	at TaskWithResult.call(ExecutorServiceTest.java:56)
	at TaskWithResult.call(ExecutorServiceTest.java:1)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

分析:10个任务,只有3条打印输出结果了,而某个线程抛出异常后,触发通知,让整个执行器关闭,那么剩余的未完成任务也提前终止了
在这里插入图片描述



参考:
《ExecutorService的submit方法使用》

<think>好的,我现在需要帮用户解决关于Java多线程ExecutorService机制的关系及用法的问题。首先,我需要理清楚用户的需求。用户可能已经了解了一些多线程的基础知识,比如通过继承Thread类或实现Runnable接口,现在想深入理解ExecutorService如何与机制结合使用,以及相关的教程。 首先,我应该回顾一下ExecutorService的基本概念,确保自己记得正确。ExecutorService是Java提供的一个高级线程管理工具,用于管理线程池,执行异步任务。它比直接创建线程更高效,因为可以复用线程,减少开销。接下来是机制,Java中主要有synchronized关键字和Lock接口,比如ReentrantLock,用于控制多线程对共享资源的访问。 用户的问题是将两者结合起来使用,即在ExecutorService管理的多线程环境中如何正确使用机制来保证线程安全。这涉及到线程池的任务提交、的获取与释放,以及可能出现的并发问题,比如竞态条件、死等。 接下来,我需要考虑用户可能的实际应用场景。例如,在处理大量并发任务时,多个线程可能同时访问共享资源,如数据库连接、文件写入或者内存中的数据结构。如果没有适当的机制,会导致数据不一致或其他问题。因此,用户可能想知道如何在ExecutorService的任务中使用来避免这些问题。 然后,我需要组织回答的结构。首先介绍ExecutorService机制的基本概念,然后说明它们如何结合使用,给出具体的代码示例,比如使用synchronized块或ReentrantLock。同时,需要强调的正确使用,避免死,以及在使用线程池时需要注意的资源管理问题。 在引用部分,用户提供了四个引用,其中引用[1]、[3]、[4]涉及ExecutorService,引用[2]涉及Thread和Runnable。需要确保回答中引用正确的部分,比如在讲解ExecutorService时引用[3]和[4],在提到线程创建时引用[2]。 可能还需要考虑用户可能遇到的常见问题,比如任务之间的依赖、的粒度、性能影响等。此外,示例代码需要符合用户提到的格式要求,比如行内数学公式使用$...$,但这里可能不涉及数学公式,但代码块需要正确展示。 最后,生成相关问题,帮助用户进一步学习,比如的类型、线程池配置、死处理等。这些问题应该与用户的问题紧密相关,并且引导他们深入理解多线程编程中的关键点。</think>### Java ExecutorService机制的多线程使用教程 #### 一、ExecutorService机制的关系 1. **ExecutorService的作用** 通过线程池管理多线程任务,优化资源利用和任务调度[^3][^4]。例如: ```java ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> System.out.println("Task running")); ``` 2. **机制的必要性** 多线程并发访问共享资源时(如变量、文件、数据库),需通过(如`synchronized`或`ReentrantLock`)保证数据一致性[^2]。 #### 二、结合使用的核心步骤 1. **定义共享资源与** ```java private static int counter = 0; private static final Lock lock = new ReentrantLock(); ``` 2. **在任务中加** ```java executor.submit(() -> { lock.lock(); try { counter++; // 临界区操作 } finally { lock.unlock(); } }); ``` 3. **使用`synchronized`简化** ```java public synchronized void increment() { counter++; } ``` #### 三、完整示例 ```java import java.util.concurrent.*; public class LockExample { private static int counter = 0; private static final Lock lock = new ReentrantLock(); public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < 1000; i++) { executor.submit(() -> { lock.lock(); try { counter++; // 线程安全操作 } finally { lock.unlock(); } }); } executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.MINUTES); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final counter: " + counter); // 输出1000 } } ``` #### 四、关键注意事项 1. **粒度控制** 范围过大(如整个方法)会降低并发性能,需尽量缩小临界区。 2. **死规避** 避免嵌套获取多个,或使用`tryLock()`设置超时: ```java if (lock.tryLock(1, TimeUnit.SECONDS)) { try { /* 操作 */ } finally { lock.unlock(); } } ``` 3. **线程池选择** - `newCachedThreadPool`:适合短时任务 - `newFixedThreadPool`:控制最大并发数[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值