学习笔记之Java的异步实现方式

学习笔记之Java的异步实现方式

异步的八种实现方式:1.线程Thread 2.Future(少用) 3.异步框架CompletableFuture(是Future的升级版) 4.Spring注解@Async(重点) 5.Spring ApplicationEvent 6.消息队列 7.第三方异步框架,比如Hutool(糊涂库) 的 ThreadUti

  1. Guava异步

    Executor线程池,也叫任务调度器。大部分异步实现都是new这个对象,然后调用这个对象的方法。

这里我挑选几个重点讲吧,以用户下单的场景为例子:

image-20230913204710465

这里发送短信要等赠送积分执行完才可以开始执行,其实发送短信和赠送积分没有先后关系,可以异步执行的

所以改进后是这样:

image-20230913204910700

线程异步

public class AsyncThread extends Thread {

  @Override
  public void run() {
    System.out.println("Current thread name:" + Thread.currentThread().getName() + " Send email success!");
  }

  public static void main(String[] args) {
    AsyncThread asyncThread = new AsyncThread();
    asyncThread.run();
  }
}

如果每次都创建一个Thread线程,频繁的创建、销毁,浪费系统资源,我们可以采用线程池:

private ExecutorService executorService = Executors.newCachedThreadPool();

public void fun() {
  executorService.submit(new Runnable() {
    @Override
    public void run() {
      log.info("执行业务逻辑...");
    }
  });
}

可以将业务逻辑封装到RunnableCallable中,交由线程池来执行。

Spring的@Async异步

自定义异步线程池

*/**
 \* 线程池参数配置,多个线程池实现线程池隔离,@Async注解,默认使用系统自定义线程池,可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async("taskName")
@EnableAsync
@Configuration
public class TaskPoolConfig {
  /**
   \* 自定义线程池
   \*
   **/*
  @Bean("taskExecutor")
  public Executor taskExecutor() {
    *//返回可用处理器的Java虚拟机的数量 12*
    int i = Runtime.getRuntime().availableProcessors();
    System.out.println("系统最大线程数 : " + i);
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    *//核心线程池大小*
    executor.setCorePoolSize(16);
    *//最大线程数*
    executor.setMaxPoolSize(20);
    *//配置队列容量,默认值为Integer.MAX_VALUE*
    executor.setQueueCapacity(99999);
    *//活跃时间*
    executor.setKeepAliveSeconds(60);
    *//线程名字前缀*
    executor.setThreadNamePrefix("asyncServiceExecutor -");
    *//设置此执行程序应该在关闭时阻止的最大秒数,以便在容器的其余部分继续关闭之前等待剩余的任务完成他们的执行*
    executor.setAwaitTerminationSeconds(60);
    *//等待所有的任务结束后再关闭线程池*
    executor.setWaitForTasksToCompleteOnShutdown(true);
    return executor;
  }
}

AsyncService

public interface AsyncService {

  MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);

  MessageResult sendEmail(String email, String subject, String content);
}

@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {

  @Autowired
  private IMessageHandler mesageHandler;

  @Override
  @Async("taskExecutor")
  public MessageResult sendSms(String callPrefix, String mobile, String actionType, String content) {
    try {

​      Thread.sleep(1000);
​      mesageHandler.sendSms(callPrefix, mobile, actionType, content);

​    } catch (Exception e) {
​      log.error("发送短信异常 -> ", e)
​    }
  }
  
  @Override
  @Async("taskExecutor")
  public sendEmail(String email, String subject, String content) {
​    try {

​      Thread.sleep(1000);
​      mesageHandler.sendsendEmail(email, subject, content);

​    } catch (Exception e) {
​      log.error("发送email异常 -> ", e)
​    }
  }
}

在实际项目中, 使用@Async调用线程池,推荐等方式是是使用自定义线程池的模式,不推荐直接使用@Async直接实现异步。

Spring ApplicationEvent事件实现异步

定义事件

public class AsyncSendEmailEvent extends ApplicationEvent {

    /**
     * 邮箱
     **/
    private String email;

   /**
     * 主题
     **/
    private String subject;

    /**
     * 内容
     **/
    private String content;
  
    /**
     * 接收者
     **/
    private String targetUserId;

}

定义事件处理器

@Slf4j
@Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {

    @Autowired
    private IMessageHandler mesageHandler;
    
    @Async("taskExecutor")
    @Override
    public void onApplicationEvent(AsyncSendEmailEvent event) {
        if (event == null) {
            return;
        }

        String email = event.getEmail();
        String subject = event.getSubject();
        String content = event.getContent();
        String targetUserId = event.getTargetUserId();
        mesageHandler.sendsendEmailSms(email, subject, content, targerUserId);
      }
}

另外,可能有些时候采用ApplicationEvent实现异步的使用,当程序出现异常错误的时候,需要考虑补偿机制,那么这时候可以结合Spring Retry重试来帮助我们避免这种异常造成数据不一致问题。

ThreadUtil异步工具类(比较常用)

@Slf4j
public class ThreadUtils {

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            ThreadUtil.execAsync(() -> {
                ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
                int number = threadLocalRandom.nextInt(20) + 1;
                System.out.println(number);
            });
            log.info("当前第:" + i + "个线程");
        }

        log.info("task finish!");
    }
}

参考文章:https://mp.weixin.qq.com/s/c_DdS667eJ8c3BpDVuxP4Q

posted @   程序侠  阅读(170)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
点击右上角即可分享
微信分享提示