java常用异步说明

简介

异步编程是一种编程范式,允许在等待某些操作(如IO操作)完成的过程中,暂停当前线程的执行,让出控制权,从而允许其他操作或线程并发执行。在Java中,有多种实现异步编程的方式,包括使用FutureCallable接口、CompletableFuture类以及ExecutorService等。

下面是一个简单的Java异步编程示例,使用了FutureCallable接口:

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

@AutowiredSpring 框架中的一个注解,用于自动注入依赖。在这个例子中,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

CompletableFutureJava 8 引入的一个异步编程工具类,它可以帮助我们以非阻塞的方式执行任务,提高程序的执行效率。下面是 CompletableFuture 的优缺点、使用场景以及具体代码示例:

优点:

  • 异步执行:CompletableFuture 可以异步地执行任务,不会阻塞主线程,提高了程序的执行效率。
  • 链式调用:CompletableFuture 支持链式调用,可以将多个异步任务串联起来,形成任务流水线,提高了代码的可读性和可维护性。
  • 异常处理:CompletableFuture 提供了丰富的异常处理方法,可以方便地处理异步任务中的异常情况。
  • 组合操作:CompletableFuture 支持多种组合操作,如 thenApplythenAcceptthenCompose 等,可以方便地对异步任务的结果进行处理。

缺点:

  • 学习成本: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

ThreadPoolExecutorJava 并发编程中的一个重要组件,它可以用来创建和管理线程池。下面是 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(51060, 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(
            82010, 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

ScheduledExecutorServiceJava中用于执行定时任务的接口,它是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。因此,需要根据系统的资源情况来合理地管理异步任务的数量。

  • 错误处理:在异步操作中,错误处理变得更加复杂。因为操作是异步的,所以异常可能不会立即显现。需要确保有合适的机制来捕获和处理这些异常。

  • 代码复杂性:异步编程可能会使代码逻辑更加复杂,特别是当涉及到回调函数嵌套时。这可能导致代码难以理解和维护。

  • 线程安全:在多线程环境下,需要特别注意数据一致性和线程安全问题。可能需要使用同步机制来保护共享数据。

  • 调试难度:由于异步操作的非顺序性,调试可能会变得困难。需要使用专门的工具和技术来跟踪和调试异步代码。

  • 测试复杂性:异步代码的测试可能更加复杂,因为需要考虑到并发和时间依赖的因素。

  • 框架和库的支持:在使用异步编程时,需要确保所使用的框架和库支持异步操作,并且能够正确地处理异步任务。

  • 性能考量:虽然异步可以提高性能,但过度使用或不当使用也可能导致性能问题。需要仔细评估和测试以确保异步操作带来的性能提升。

  • 用户体验:在用户界面程序中,适当的异步操作可以避免界面冻结,提供更好的用户体验。

posted @ 2024-05-13 17:42  楚景然  阅读(49)  评论(0编辑  收藏  举报