简介
异步编程是一种编程范式,允许在等待某些操作(如IO操作)完成的过程中,暂停当前线程的执行,让出控制权,从而允许其他操作或线程并发执行。在Java中,有多种实现异步编程的方式,包括使用Future
和Callable
接口、CompletableFuture
类以及ExecutorService
等。
下面是一个简单的Java异步编程示例,使用了Future
和Callable
接口:
import java.util.concurrent.*;
public class AsyncDemo {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交一个异步任务
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 模拟耗时操作
Thread.sleep(1000);
return 42;
}
});
// 在等待结果的过程中,主线程可以继续执行其他任务
System.out.println("Doing other work...");
try {
// 获取异步任务的结果
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
// 关闭线程池
executor.shutdown();
}
}
}
常用异步编程方式
1.注入 ThreadPoolTaskExecutor
@Autowired
是 Spring
框架中的一个注解,用于自动注入依赖。在这个例子中,ThreadPoolTaskExecutor
是一个线程池任务执行器,用于执行异步任务。
优点:
- 提高系统性能:通过使用线程池,可以有效地利用系统资源,避免频繁地创建和销毁线程,从而提高系统性能。
- 易于管理:线程池提供了对线程的集中管理,可以方便地调整线程池的大小、优先级等参数。
- 提供任务调度功能:线程池可以根据任务的优先级、延迟时间等参数进行任务调度,确保高优先级的任务优先执行。
缺点:
- 资源消耗:线程池会占用一定的系统资源,如内存、CPU 等,如果配置不当,可能会导致资源浪费。
- 可能导致死锁:在多线程环境下,如果没有正确地使用同步机制,可能会导致死锁等问题。
场景使用:
- 异步处理:当需要执行耗时操作时,可以使用线程池将任务放入队列中,由线程池中的线程异步执行,提高系统的响应速度。
- 定时任务:线程池可以用于执行定时任务,如定时清理缓存、定时发送邮件等。
无返回demo:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class TaskExecutorDemo {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public void executeTask() {
taskExecutor.execute(() -> {
// 这里是需要执行的任务代码
System.out.println("任务执行中...");
});
}
}
有返回demo
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
public class TaskExecutorDemo {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public Future<String> executeTask() {
return taskExecutor.submit(() -> {
// 这里是需要执行的任务代码
System.out.println("任务执行中...");
return "任务执行结果";
});
}
}
future.get()
方法会阻塞当前线程,直到任务执行完成。如果任务执行过程中发生异常,future.get()
方法会抛出 ExecutionException
异常。因此,在使用 future.get()
方法时,需要进行异常处理。
2.Executors.newCachedThreadPool()
ExecutorService service = Executors.newCachedThreadPool();
这行代码创建了一个缓存线程池,它可以根据实际情况自动调整线程数量。
优点:
- 灵活性:缓存线程池可以根据任务的数量动态地创建和销毁线程,避免了固定线程数量的浪费和资源占用。
- 性能优化:当任务数量增加时,缓存线程池会自动增加线程数量来提高执行效率;当任务数量减少时,缓存线程池会自动减少线程数量以节省资源。
缺点:
- 资源消耗:由于缓存线程池会根据任务数量动态调整线程数量,可能会导致系统资源的过度使用和浪费。
- 线程管理复杂:缓存线程池需要对线程进行动态管理,增加了系统的复杂度和维护成本。
场景使用:
- 大量短周期任务:当有大量短周期任务需要执行时,可以使用缓存线程池来提高执行效率。
- 不确定任务数量:当任务数量不确定或者经常变化时,缓存线程池可以根据实际情况自动调整线程数量,提高系统的稳定性和性能。
代码demo
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
// 这里是需要执行的任务代码
System.out.println("任务执行中...");
}
});
}
}
3. CompletableFuture
CompletableFuture
是 Java 8
引入的一个异步编程工具类,它可以帮助我们以非阻塞的方式执行任务,提高程序的执行效率。下面是 CompletableFuture
的优缺点、使用场景以及具体代码示例:
优点:
- 异步执行:
CompletableFuture
可以异步地执行任务,不会阻塞主线程,提高了程序的执行效率。 - 链式调用:
CompletableFuture
支持链式调用,可以将多个异步任务串联起来,形成任务流水线,提高了代码的可读性和可维护性。 - 异常处理:
CompletableFuture
提供了丰富的异常处理方法,可以方便地处理异步任务中的异常情况。 - 组合操作:
CompletableFuture
支持多种组合操作,如thenApply
、thenAccept
、thenCompose
等,可以方便地对异步任务的结果进行处理。
缺点:
- 学习成本:
CompletableFuture
的使用相对复杂,需要一定的学习成本。 - 错误处理:
CompletableFuture
的错误处理方式较为繁琐,需要手动处理异常或者使用try-catch
语句。
使用场景:
- 异步IO操作:在需要进行网络请求、文件读写等耗时操作时,可以使用
CompletableFuture
进行异步处理,提高程序的执行效率。 - 并发编程:在多线程环境下,可以使用
CompletableFuture
进行任务的并行处理,提高程序的执行效率。 - 异步计算:在需要进行大量计算的场景下,可以使用
CompletableFuture
进行异步计算,提高程序的执行效率。
代码demo
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CompletableFutureDemo {
public static void main(String[] args) {
// 创建一个异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, CompletableFuture!";
});
// 对异步任务的结果进行处理
future.thenAccept(result -> System.out.println("Result: " + result));
// 等待异步任务完成
future.join();
}
}
4. ThreadPoolExecutor
ThreadPoolExecutor
是 Java
并发编程中的一个重要组件,它可以用来创建和管理线程池。下面是 ThreadPoolExecutor
的优缺点、使用场景以及具体代码示例:
优点:
- 资源重用:
ThreadPoolExecutor
可以复用已创建的线程,减少了线程创建和销毁的开销。 - 管理线程:
ThreadPoolExecutor
提供了丰富的线程管理功能,如线程池大小、任务队列等,方便了线程的管理和维护。 - 提高性能:通过合理地配置线程池参数,可以提高程序的性能。
- 异常处理:
ThreadPoolExecutor
提供了异常处理机制,可以方便地处理线程执行过程中的异常情况。
缺点:
- 学习成本:
ThreadPoolExecutor
的使用相对复杂,需要一定的学习成本。 - 错误处理:
ThreadPoolExecutor
的错误处理方式较为繁琐,需要手动处理异常或者使用try-catch
语句。
使用场景:
- 高并发场景:在需要进行大量并发操作的场景下,可以使用
ThreadPoolExecutor
来提高程序的执行效率。
I- O密集型任务:在需要进行大量 IO 操作的场景下,可以使用ThreadPoolExecutor
来提高程序的执行效率。 - 定时任务:在需要进行定时任务的场景下,可以使用
ThreadPoolExecutor
来实现任务的定时执行。
demo
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 提交任务到线程池
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println("Task executed by thread: " + Thread.currentThread().getName());
});
}
// 关闭线程池
executor.shutdown();
}
}
有返回值
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建一个固定大小的线程池
private ThreadPoolExecutor threadPoolServiceGuideOpenIdByBuyerFxy = new ThreadPoolExecutor(
8, 20, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(1000),
new ThreadFactoryBuilder().setNameFormat("xxx-check-pool-%d").build(),
new ThreadPoolExecutor.DiscardOldestPolicy());
// 提交任务到线程池并获取 Future 对象
Future<Integer> future = executor.submit(() -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
});
// 获取任务执行结果
int result = future.get();
System.out.println("Task result: " + result);
// 关闭线程池
executor.shutdown();
}
}
这段代码创建了一个名为threadPoolServiceGuideOpenIdByBuyerFxy的线程池对象。该线程池具有以下配置:
核心线程数(corePoolSize):8个线程
最大线程数(maximumPoolSize):20个线程
空闲线程存活时间(keepAliveTime):10秒
时间单位(timeUnit):秒
任务队列(workQueue):一个容量为1000的ArrayBlockingQueue
线程工厂(threadFactory):使用ThreadFactoryBuilder设置线程名称格式为"fxy-item-check-pool-%d"
拒绝策略(rejectedExecutionHandler):DiscardOldestPolicy,即丢弃最旧的任务
这个线程池可以用于执行一些需要并发处理的任务,例如检查购买者信息、处理商品等操作。
5. @Async注解
Spring
的@Async
注解是用于实现异步方法调用的。它可以将一个方法标记为异步执行,使得该方法在单独的线程中运行,而不会阻塞主线程。
优点:
- 提高程序的性能和响应速度:通过将耗时的操作放在单独的线程中执行,可以避免主线程被阻塞,从而提高程序的吞吐量和响应速度。
- 简化代码:使用@Async注解可以简化异步方法的编写,无需手动创建线程或使用线程池。
- 易于管理:
Spring
框架提供了对异步方法的管理和监控,可以方便地跟踪异步任务的状态和结果。
缺点:
- 复杂性增加:使用
@Async
注解会增加代码的复杂性,需要处理异步任务的生命周期和异常情况。 - 线程安全问题:异步方法可能会涉及到共享资源的访问,需要注意线程安全问题,避免出现竞态条件和数据不一致的情况。
场景使用:
- 高并发场景:对于需要处理大量请求和大量数据的应用程序,可以使用
@Async
注解来提高系统的并发能力和响应速度。 - 耗时操作:对于一些耗时较长的操作,如数据库查询、文件读写等,可以使用
@Async
注解将其异步执行,避免阻塞主线程。
具体代码demo:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncDemo {
@Async
public void asyncMethod() {
// 异步执行的代码逻辑
System.out.println("异步方法执行中...");
}
}
@Async线程池
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
上述配置创建了一个名为"asyncExecutor"的线程池,其中pool-size设置为10,表示线程池中的线程数量为10。你可以根据实际需求调整pool-size的值。
接下来,你可以在需要异步执行的方法上添加@Async注解,并指定使用的线程池名称:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncDemo {
@Async("asyncExecutor")
public void asyncMethod() {
// 异步执行的代码逻辑
System.out.println("异步方法执行中...");
}
}
6. ScheduledExecutorService
ScheduledExecutorService
是Java
中用于执行定时任务的接口,它是ExecutorService
的一个子接口。它提供了一种方便的方式来调度和执行延迟或周期性的任务。
优点:
- 灵活性:
ScheduledExecutorService
允许你以不同的时间单位来指定任务的延迟或周期执行。 - 可配置性:你可以控制线程池的大小、任务的优先级等参数,以满足特定的需求。
- 易于使用:它提供了简单的方法来提交和取消任务,以及获取任务的状态和结果。
缺点:
- 资源消耗:如果创建过多的线程或者任务数量过多,可能会导致系统资源的浪费和性能下降。
- 异常处理:在任务执行过程中可能会抛出异常,需要适当处理这些异常以避免程序崩溃。
场景使用:
- 定时任务:适用于需要按照固定的时间间隔或者特定时间点执行的任务,例如定时清理缓存、定时发送邮件等。
- 周期性任务:适用于需要周期性执行的任务,例如定期检查系统状态、定期备份数据等。
具体代码demo:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
// 创建一个ScheduledExecutorService实例
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 定义一个Runnable任务
Runnable task = () -> {
System.out.println("Task executed at " + new Date());
};
// 提交任务并设置延迟执行时间
scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);
// 关闭ScheduledExecutorService
scheduledExecutorService.shutdown();
}
}
Java异步操作的优缺点
Java异步操作的优点具体如下:
- 提高并发性:异步操作允许多个任务并行执行,这样可以更好地利用系统资源,尤其是在多核处理器上。
- 提升响应性:在处理耗时的IO操作时,异步操作可以避免阻塞主线程,从而提高应用程序的响应速度。
Java异步操作的缺点具体如下:
- 编程复杂性:异步编程通常需要处理回调函数、状态同步等复杂的逻辑,这可能会增加代码的复杂性和出错的风险。
- 线程安全:在多线程环境下,需要确保数据的一致性和线程安全,这可能需要使用额外的同步机制。
- 性能开销:虽然异步操作可以提高并发性,但是过多的线程可能会导致上下文切换和同步的开销,从而影响性能。
使用异步考虑
任务的性质:对于IO密集型或需要等待外部资源的任务,如网络请求、文件读写等,使用异步可以显著提高效率。然而,对于计算密集型任务,由于Java的线程模型,过多的线程可能导致上下文切换,反而降低性能。
系统资源:异步操作通常意味着更多的线程或任务在后台运行。这可能会导致系统资源的消耗增加,如内存和CPU。因此,需要根据系统的资源情况来合理地管理异步任务的数量。
错误处理:在异步操作中,错误处理变得更加复杂。因为操作是异步的,所以异常可能不会立即显现。需要确保有合适的机制来捕获和处理这些异常。
代码复杂性:异步编程可能会使代码逻辑更加复杂,特别是当涉及到回调函数嵌套时。这可能导致代码难以理解和维护。
线程安全:在多线程环境下,需要特别注意数据一致性和线程安全问题。可能需要使用同步机制来保护共享数据。
调试难度:由于异步操作的非顺序性,调试可能会变得困难。需要使用专门的工具和技术来跟踪和调试异步代码。
测试复杂性:异步代码的测试可能更加复杂,因为需要考虑到并发和时间依赖的因素。
框架和库的支持:在使用异步编程时,需要确保所使用的框架和库支持异步操作,并且能够正确地处理异步任务。
性能考量:虽然异步可以提高性能,但过度使用或不当使用也可能导致性能问题。需要仔细评估和测试以确保异步操作带来的性能提升。
用户体验:在用户界面程序中,适当的异步操作可以避免界面冻结,提供更好的用户体验。