Java异步编程(二):为什么我的异步任务没被执行

文章通过一个例子展示了为何异步任务可能未被执行,解释了JVM不会等待守护线程(如ForkJoinPool.commonPool())的原因。解决方案是使用自定义线程池,并通过ExecutorService的shutdown()和awaitTermination()方法确保线程池关闭和任务执行完毕。
摘要由CSDN通过智能技术生成

1、概述

大家好,我是欧阳方超。在上一篇文章中——,我们介绍了CompletableFuture的初步使用,只是在那里我们并没有从代码的角度验证是程序异步执行的,今天就来看一下。

2、为什么我的异步任务没被执行

2.1、真的是异步吗

将上一篇文章中的代码稍作调整:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() ->  "期望后输出");

        future.thenAccept(s -> {
            System.out.println(s);
        });

        System.out.println("期望首先输出");

    }
}

程序输出结果:

期望后输出
期望首先输出

这个输出结果就值得思考了,为什么不是先输出“期望首先输出”这一行文字,而是先输出了“期望后输出”呢,按理说主线程提交了一个异步任务,异步任务由其他线程执行,主线程不等待异步任务继续往下执行,好吧,其实这种情况就是上面代码中异步任务完成的速度可能比较快,因此thenAccept()方法注册的Consumer函数会在主线程输出第一个语句之前就已经被调用了,从而导致输出了上面的结果。
不想让异步任务执行那么快,那么首先想到的是使用sleep()方法模拟一个耗时任务,具体改动如下。

2.2、模拟耗时任务

在异步任务中模拟耗时任务,代码如下:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {


        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "期望后输出";
        });

        future.thenAccept(s -> {
            System.out.println(s);
        });
        System.out.println("期望首先输出");

    }
}

此时只见程序输出了:

期望首先输出

异步任务中的字符串根本没有输出,为什么呢?实际上,JVM只会等待new出来的线程执行完毕后才会退出,不会等待守护进程,而CompletableFuture底层默认使用了ForkJoinPool.commonPool()方法返回的线程,这些线程都是守护线程,所以主线程一旦结束,不会等待异步任务的完成了,进而JVM也退出了。异步任务没有执行,这显然不是我们想要的结果,有办法解决吗,当然有,那就是使用自定义线程池。

2.4、使用自定义线程池

CompletableFuture默认使用的是ForkJoinPool.commonPool()方法返回的线程,好在CompletableFuture也提供了相关方法允许使用自定义的线程池,代码如下:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "期望后输出";
        }, executorService);

        future.thenAccept(s -> {
            System.out.println(s);
        });
        System.out.println("期望首先输出");
        
    }
}

上面代码中我们使用Executors.newFixedThreadPool(1)创建了里面包含一个线程的线程池executorService ,调用supplyAsync()方法就会把异步任务提交给executorService ,该异步任务不会立即执行(只会在合适的时机执行),supplyAsync()方法会返回一个future对象,接着主线程继续往下执行System.out.println(“期望首先输出”)语句,接着异步任务完成,thenAccept()回到才运行,最终程序输出:

期望首先输出
期望后输出

但是这个时候有个问题,就是JVM会一直不退出,因为我们自己创建的线程池依然存活着呢,还在等待任务进来,也就是我们没有对其进行关闭,接下来看如何关闭线程池。

2.4、关闭线程池

其实关闭线程池可以使用shutdown()方法,代码如下:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "期望后输出";
        }, executorService);

        future.thenAccept(s -> {
            System.out.println(s);
        });
        executorService.shutdown();
        System.out.println("期望首先输出");
    }
}

上面的程序输出:

期望首先输出
期望后输出

2.5、关闭线程池——更进一步

如果我们只是使用shutdown()来关闭线程池,它会触发之前提交过来的任务按顺序执行完,并且此时只是不在接收新提交过来的任务;还应该调用boolean awaitTermination(long timeout, TimeUnit unit)方法等待线程池中的任务执行完成,该方法接收两个参数,一个是时长,一个是时长单位,该方法会一直阻塞直到下面条件中的其中一个首先发生:在shutdown()请求之后任务成功完成、超时时间已到、当前线程被打断,如果在设置的超时时间内任务执行完了该方法返回true、如果超时了就返回false,下面的代码中使用了boolean awaitTermination(long timeout, TimeUnit unit)方法:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "期望后输出";
        }, executorService);

        future.thenAccept(s -> {
            System.out.println(s);
        });
        executorService.shutdown();
        boolean b = executorService.awaitTermination(5, TimeUnit.SECONDS);
        if (b) {
            System.out.println("没问题");
        } else {
            System.out.println("有问题");
        }
        System.out.println("期望首先输出");
    }
}

输出结果:

期望后输出
没问题
期望首先输出

awaitTermination()方法等待5s,异步任务中的任务等待1s,在等待时间内异步任务完全有时间执行完,返回此时该方法返回了true,注意,异步任务执行完后future.thenAccept()方法是由主线程执行的,所有输出顺序才是上面的结果。
如果我们将awaitTermination()方法等待1s,异步任务中的任务等待5s,awaitTermination()方法将返回false,程序输出结果也会发生变了,详见下方调整后的程序:

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "期望后输出";
        }, executorService);

        future.thenAccept(s -> {
            System.out.println(s);
        });
        executorService.shutdown();
        boolean b = executorService.awaitTermination(1, TimeUnit.SECONDS);
        if (b) {
            System.out.println("没问题");
        } else {
            System.out.println("有问题");
        }
        System.out.println("期望首先输出");
    }
}

输出结果:

有问题
期望首先输出
期望后输出

3、总结

今天的介绍,我们从一个小的异步程序开始,到模拟耗时任务,再到用自定义线程池执行异步任务,最后谈到线程池的关闭,从一些方面了解了为什么有时候异步任务没有被执行。
我是欧阳方超,把事情做好了自然就有兴趣了,如果你喜欢我的文章,欢迎点赞、转发、评论加关注。我们下次见。

Java中,可以使用AOP框架来实现在异步方法中执行AOP方法。具体步骤如下: 1. 定义一个切面类,包含需要执行的AOP方法。 2. 在需要执行AOP方法的地方,使用AOP框架的@Aspect注解标识该类是一个切面类。 3. 使用AOP框架的@Around注解标识需要执行AOP方法的方法,在@Around注解中使用ProceedingJoinPoint参数来获取异步方法的执行信息。 4. 在@Around注解中,使用proceed()方法来执行异步方法,并在执行异步方法前后调用需要执行的AOP方法。 下面是一个简单的示例代码: 切面类: ```java @Aspect @Component public class AsyncAspect { @Around("@annotation(org.springframework.scheduling.annotation.Async)") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { // 异步方法执行前调用的AOP方法 System.out.println("Before async method execution..."); // 执行异步方法 Object result = joinPoint.proceed(); // 异步方法执行后调用的AOP方法 System.out.println("After async method execution..."); return result; } } ``` 异步方法: ```java @Service public class AsyncService { @Async public void asyncMethod() { // 异步方法的具体实现 System.out.println("Async method is executed..."); } } ``` 在上面的示例中,@Aspect注解标识AsyncAspect类是一个切面类,@Around注解标识doAround()方法需要执行AOP方法的地方,使用ProceedingJoinPoint参数获取异步方法的执行信息,然后在执行异步方法前后调用需要执行的AOP方法。异步方法使用@Async注解标识,表示该方法是一个异步方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值