Java多线程之ExecutorService

本文详细解析Java多线程编程的核心概念及工具,包括Thread、Runnable、synchronized等基本元素,以及从JDK1.5开始引入的java.util.concurrent包,涵盖并发集合类、线程池机制、同步互斥机制、线程安全变量更新工具类和锁等常用工具。通过一个实际场景案例,展示如何高效利用多线程提高检查效率。

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

讲到Java多线程,大多数人脑海中跳出来的是Thread、Runnable、synchronized……这些是最基本的东西,虽然已经足够强大,但想要用好还真不容易。从JDK 1.5开始,增加了java.util.concurrent包,它的引入大大简化了多线程程序的开发(要感谢一下大牛Doug Lee)。 java.util.concurrent包分成了三个部分,分别是java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.lock。内容涵盖了并发集合类、线程池机制、同步互斥机制、线程安全的变量更新工具类、锁等等常用工具。 为了便于理解,本文使用一个例子来做说明,交代一下它的场景: 假设要对一套10个节点组成的环境进行检查,这个环境有两个入口点,通过节点间的依赖关系可以遍历到整个环境。依赖关系可以构成一张有向图,可能存在环。为了提高检查的效率,考虑使用多线程。

  在稍老一些的Java版本中,具体是在Java 5.0以前,启动一个任务是通过调用Thread类的start()方法来实现的,任务的提交和执行时同时进行的,如果想对任务的执行进行调度,或者想控制同时执行的线程数量就需要额外编写代码来完成。而在Java 5.0里提供了一个新的任务执行架构使我们可以很轻松地调度和控制任务的执行。这个架构主要有三个接口和其相应的类组成。这三个接口分别是:Executor、ExevutirService和ScheduledExecutorService。下面我们来具体说明。

Executor接口:是用来执行Runnable任务的,它值定义了一个方法:

  execute(Runnable command):执行Ruannable类型的任务.

ExecutorService接口:ExecutorService继承了Executor的方法,并提供了执行Callable任务和中指任务执行的服务,其定义的方法主要有:

  submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象

  invokeAll(collection of tasks):批处理任务集合,并返回一个代表这些任务的Future对象集合

  shutdown():在完成已提交的任务后关闭服务,不再接受新任务

  shutdownNow():停止所有正在执行的任务并关闭服务。

  isTerminated():测试是否所有任务都执行完毕了。

  isShutdown():测试是否该ExecutorService已被关闭

ScheduledExecutorService接口:

在ExecutorService的基础上,ScheduledExecutorService提供了按时间安排执行任务的功能,它提供的方法主要有:

  schedule(task, initDelay): 安排所提交的Callable或Runnable任务在initDelay指定的时间后执行

  scheduleAtFixedRate():安排所提交的Runnable任务按指定的间隔重复执行

  scheduleWithFixedDelay():安排所提交的Runnable任务在每次执行完后,等待delay所指定的时间后重复执行

_________________________________________________________________________________________________________________________________

虽然以上提到的接口有其实现类,但是为了方便,我们建议使用Executors的工具类来得到Executor接口的具体对象,需要注意的是,Executors是一个类,不是Executor的复数形式,Executor提供了一下一些静态方法:

callable(Runnable task):将一个Runnable的任务转化成callable的任务

newSingleThreadExecutor: 产生一个ExecutorService对象,这个对象只有一个线程可用来执行任务,若任务多于一个,任务将按先后顺序执行

newCachedThreadPool(): 产生一个ExecutorService对象,这个对象带有一个线程池,线程池的大小会根据需要调整,线程执行完任务后返回线程池,供执行下一次任务使用。

newFixedThreadPool(int poolSize):产生一个ExecutorService对象,这个对象带有一个大小为poolSize的线程池,若任务数量大于poolSize,任务会被放在一个queue里顺序执行

newSingleThreadScheduledExecutor:产生一个ScheduledExecutorService对象,这个对象的线程池大小为1,若任务多于一个,任务将按先后顺序执行

newScheduledThreadPool(int poolSize): 产生一个ScheduledExecutorService对象,这个对象的线程池大小为poolSize,若任务数量大于poolSize,任务会在一个queue里等待执行

 

1、Executors 通过这个类能够获得多种线程池的实例,例如可以调用newSingleThreadExecutor()获得单线程的ExecutorService,调用newFixedThreadPool()获得固定大小线程池的ExecutorService。拿到ExecutorService可以做的事情就比较多了,最简单的是用它来执行Runnable对象,也可以执行一些实现了Callable<t>的对象。用Thread的start()方法没有返回值,如果该线程执行的方法有返回值那用ExecutorService就再好不过了,可以选择submit()、invokeAll()或者invokeAny(),根据具体情况选择合适的方法即可。

2. Atomics原子级变量 原子量级的变量,主要的类有AtomicBoolean, AtomicInteger, AotmicIntegerArray, AtomicLong, AtomicLongArray, AtomicReference ……。这些原子量级的变量主要提供两个方法: compareAndSet(expectedValue, newValue):比较当前的值是否等于expectedValue,若等于把当前值改成newValue,并返回true。若不等,返回false。 getAndSet(newValue):把当前值改为newValue,并返回改变前的值。 这些原子级变量利用了现代处理器(CPU)的硬件支持可把两步操作合为一步的功能,避免了不必要的锁定,提高了程序的运行效率。

<think>好的,我现在需要帮用户解决关于Java多线程ExecutorService与锁机制的关系及用法的问题。首先,我需要理清楚用户的需求。用户可能已经了解了一些多线程的基础知识,比如通过继承Thread类或实现Runnable接口,现在想深入理解ExecutorService如何与锁机制结合使用,以及相关的教程。 首先,我应该回顾一下ExecutorService的基本概念,确保自己记得正确。ExecutorServiceJava提供的一个高级线程管理工具,用于管理线程池,执行异步任务。它比直接创建线程更高效,因为可以复用线程,减少开销。接下来是锁机制,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、付费专栏及课程。

余额充值