java实现异步任务功能的方法

异步简介

异步编程是一种编程模式,通过将任务分解为多个子任务,并在后台或并行线程中执行这些子任务,以提高程序的性能和响应能力。

一、继承 Thread 类实现异步

1、继承Thread子类需要用到的方法

方法名说明
void run()在线程开启后,此方法将被调用执行,不能直接调用该方法实现多线程
void start()使此方法开启一个新线程并开始执行,Java虚拟机会自动调用 run方法

2、实现步骤

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

3、举个例子

public class MyThreadDemo extends Thread {
    @Override
    public void run() {
        Long start = System.currentTimeMillis();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        Long end = System.currentTimeMillis();
        System.out.println("线程耗时:"+(end-start)+"毫秒");
    }
}
 public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        MyThreadDemo m1 = new MyThreadDemo();
        MyThreadDemo m2 = new MyThreadDemo();
        m1.start();
        m2.start();
        Long end = System.currentTimeMillis();
        System.out.println("总线程耗时:"+(end-start)+"毫秒");
    }

实现结果:
在这里插入图片描述
总结: 继承 Thread 类这种实现方式,实现比较简单,但是扩展性差,因为类只能单继承。

二、通过新建匿名线程来实现异步

1、实现方法

new Thread(()->{
	执行代码块;
}).start();

2、举例说明

 Long start = System.currentTimeMillis();
        new Thread(() -> {
            Long start1 = System.currentTimeMillis();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Long end1 = System.currentTimeMillis();
            System.out.println("线程耗时:"+(end1-start1)+"毫秒");
        }).start();
        Long end = System.currentTimeMillis();
        System.out.println("总线程耗时:"+(end-start)+"毫秒");

实现结果:
在这里插入图片描述
注意: JDK8以上版本支持该方法
【Java异常】 Error:java: Compilation failed: internal java compiler error 的解决方案

三、实现 Runnable 接口

1、需要用到的 Thread 构造方法介绍:

方法名说明
Thread(Runnable target)传入实现了 Runnable 接口的类,构造一个 Thread 对象
Thread(Runnable target, String name)传入实现了 Runnable 接口的类,构造一个名称为 name 的 Thread 对象

2、实现步骤:

  • 定义一个类 MyRunnable 实现 Runnable 接口
  • 在 MyRunnable 类中实现 run() 方法
  • 创建MyRunnable 类的对象
  • 创建 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
  • 启动线程

3、举例说明

public class AsynchronousThread  implements Runnable {
    @Override
    public void run() {
       Long start =  System.currentTimeMillis();
        try {
            Thread.sleep(1000);
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       Long end =System.currentTimeMillis();
        System.out.println("异步线程耗时:"+(end-start)+"毫秒");
    }
}
public static void main(String[] args) {
        Long start =  System.currentTimeMillis();
        AsynchronousThread at= new AsynchronousThread();
        Thread thread1= new Thread(at);
        Thread thread2 = new Thread(at);
        thread1.start();
        thread2.start();
        Long end =System.currentTimeMillis();
        System.out.println("同步线程耗时:"+(end-start)+"毫秒");
    }

实现结果:
在这里插入图片描述

总结: 实现 Runnable 接口、Callable 接口这两种实现方式,实现比较复杂,但是扩展性比较强。

四、实现 Callable 接口

1、相关方法介绍:

方法名说明
V call()这是 Callable 接口中要实现的方法,相当于 Runnable 接口中的 run 方法
FutureTask(Callable callable)使用 Callable 接口实现类实例创建一个 FutureTask,它运行时会调配用 Callable 接口中的 call 方法
V get()FutureTask实例的 get 方法,可以阻塞代码继续往下执行,直到获取到异步线程中的返回结果为止

2、实现步骤:

  • 定义一个类 MyCallable 实现 Callable 接口
  • 在 MyCallable 类中重实现 call() 方法
  • 创建 MyCallable 类的对象
  • 创建 FutureTask 对象,把 MyCallable 对象作为构造方法的参数
  • 创建 Thread 类的对象,把 FutureTask 对象作为构造方法的参数
  • 启动线程
  • 如果想获取返回值的话,可以调用get方法,就可以获取线程结束之后的结果

3、举例说明

public class CallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        Long start =  System.currentTimeMillis();
        try {
            Thread.sleep(1000);
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Long end =System.currentTimeMillis();
        System.out.println("异步线程耗时:"+(end-start)+"毫秒");
        return "返回异步线程耗时:"+(end-start)+"毫秒";
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Long start =  System.currentTimeMillis();
        CallableThread mc = new CallableThread();

        //因为该类实现了String 类型的 Callable 接口
        //所以返回值也是 String 类型,所以创建的是 String 类型的 FutureTask 对象
        FutureTask<String> ft = new FutureTask<>(mc);

        //传入 FutureTask 实例,创建线程对象
        Thread t1 = new Thread(ft);

        //不能在这个地方使用 FutureTask 的 get 方法获取异步线程的返回值,否则程序将卡死在这里。
        //因为 t1 线程还没有执行,所以无法获取到返回值,所以如果执行 get 方法,程序将卡死在这里。
        //String s = ft.get();
        //开启新线程,异步执行 MyCallable 实例中的 call 方法逻辑
        t1.start();
        //这里编写一些实现其它业务逻辑代码进行执行
        //可以做一些其它比较耗时的任务
        //......
        Thread.sleep(2000);
        //获取异步线程的返回值
        String s = ft.get();
        System.out.println(s);
        Long end =System.currentTimeMillis();
        System.out.println("同步线程耗时:"+(end-start)+"毫秒");
    }

返回结果:
在这里插入图片描述
总结: 如果想要获取到异步线程中的返回值的话,可以采用实现 Callable 接口这种实现方式。

五、Spring的@Async异步(常用)

1、@Async 注解简介

@Async 注解作用

Spring 框架提供的注解,用于将方法标记为异步执行的方法。它的作用是告诉 Spring 框架在调用被注解的方法时,将其放入线程池中异步执行,而不是阻塞等待方法的完成。

@Async 注解的工作原理

在调用被注解的方法时,Spring 会将该方法的执行转移到线程池中的一个线程进行处理。执行完成后,方法的返回值将通过 Future 或 CompletableFuture 进行封装,以便获取方法的返回结果
源码讲解

2、步骤讲解

  • 在方法上添加@Async,表示此方法是异步方法;
  • 在类上添加@Async,表示类中的所有方法都是异步方法;
  • 使用此注解的类,必须是Spring管理的类;
  • 需要在启动类或配置类中加入@EnableAsync注解,@Async才会生效;
  • 在使用@Async时,如果不指定线程池的名称,也就是不自定义线程池,@Async是有默认线程池的,使用的是Spring默认的线程池SimpleAsyncTaskExecutor。

默认线程池的默认配置如下:

  • 默认核心线程数:8;
  • 最大线程数:Integet.MAX_VALUE;//在并发情况下,会无限制的创建线程
  • 队列使用LinkedBlockingQueue;
  • 容量是:Integet.MAX_VALUE;
  • 空闲线程保留时间:60s;
  • 线程池拒绝策略:AbortPolicy;

3、Spring 已经实现的线程池

  • SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。
  • SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
  • ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
  • SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
  • ThreadPoolTaskExecutor :最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。

4、异步的方法有:

  • 最简单的异步调用,返回值为void
  • 带参数的异步调用,异步方法可以传入参数
  • 存在返回值,常调用返回Future

5、实例讲解

启动类
@EnableAsync
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
自定义线程池

@Configuration


public class AsyncTaskConfig{
	@Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池大小
        //设置核心线程数
        executor.setCorePoolSize(3);
        //最大线程数
        executor.setMaxPoolSize(6);
        //队列容量
        executor.setQueueCapacity(12);
        //活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        //线程名字前缀
        executor.setThreadNamePrefix("SyncHistoryInvoice-");
        //线程拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //初始化
        executor.initialize();
        return executor;
    }
}
异步任务
 @Async("taskExecutor")
    public void test() throws Exception {
        long sleep = random.nextInt(10000);
        log.info("开始任务,需耗时:" + sleep + "毫秒");
        Thread.sleep(sleep);
        log.info("完成任务");
        return new AsyncResult<>("test");
    }
调用异步任务
 异步任务类名.test();
拒绝策略
  • ThreadPoolExecutor.AbortPolicy()抛出java.util.concurrent.RejectedExecutionException异常 终止策略是默认的饱和策略;
  • ThreadPoolExecutor.CallerRunsPolicy()当抛出RejectedExecutionException异常时,会调rejectedExecution方法 调用者运行策略实现了一种调节机制,该策略既不会抛弃任务也不会爆出异常,而是将任务退回给调用者,从而降低新任务的流量
  • ThreadPoolExecutor.DiscardOldestPolicy()抛弃旧的任务;当新提交的任务无法保存到队列中等待执行时将抛弃最旧的任务,然后尝试提交新任务。如果等待队列是一个优先级队列,抛弃最旧的策略将导致抛弃优先级最高的任务,因此AbortPolicy最好不要和优先级队列一起使用。
  • ThreadPoolExecutor.DiscardPolicy()抛弃当前的任务

四种策略优缺点详解

6、@Async失效的几个原因:

  • 注解@Async的方法不是public方法;
  • 注解@Async的返回值只能为void或Future;
  • 注解@Async方法使用static修饰也会失效;
  • 没在启动类上加@EnableAsync注解;
  • 调用异步方法和@Async调用方法不能在一个类中;原因: 类似于Spring对@Transactional注解时也有类似问题,Spring扫描时具有@Transactional注解方法的类时,是生成一个代理类,由代理类去开启关闭事务,而在同一个类中,方法调用是在类体内执行的,Spring无法截获这个方法调用。 @Async使用的是动态代理来实现异步调用,因此不能够在同一个类中进行调用。方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
  • 在Async方法上标注@Transactional是没用的,但在Async方法调用的方法上标注@Transcational是有效的;

六、其他方法后续会补充

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值