异步线程及@Async注解

一、Thread.sleep()和TimeUnit.SECONDS.sleep()的区别与联系。

刚看到TimeUnit.SECONDS.sleep()方法时觉得挺奇怪的,这里怎么也提供sleep方法?

public void sleep(long timeout) throws InterruptedException {
    if (timeout > 0) {
        long ms = toMillis(timeout);
        int ns = excessNanos(timeout, ms);
        Thread.sleep(ms, ns);
    }
}
结果一看源码,原来是对Thread.sleep方法的包装,实现是一样的,只是多了时间单位转换和验证,然而TimeUnit枚举成员的方法却提供更好的可读性,这可能就是当初创建TimeUnit时提供sleep方法的原因吧,大家都知道sleep方法很常用,但经常要使用一个常量保存sleep的时间,比如3秒钟,我们代码通常会这样写:

private final int SLEEP_TIME = 3 * 1000; //3 seconds
因为Thread.sleep方法参数接受的毫秒单位的数值,比较下面代码就知道TimeUnit枚举成员的sleep方法更优雅:
TimeUnit.MILLISECONDS.sleep(10);
TimeUnit.SECONDS.sleep(10);
TimeUnit.MINUTES.sleep(10);
Thread.sleep(10);
Thread.sleep(10*1000);
Thread.sleep(10*60*1000);
但使用TimeUnit枚举成员的sleep方法会不会带来性能损失了,毕竟增加了函数调用开销?
测试测试吧:
import java.util.concurrent.TimeUnit;
public class TestSleep {
    public static void main(String[] args) throws InterruptedException {      
        sleepByTimeunit(10000);
                                                                                                                   
        sleepByThread(10000);     
    }
    private static void sleepByTimeunit(int sleepTimes) throws InterruptedException {
        long start = System.currentTimeMillis();
                                                                                                                   
        for(int i=0; i<sleepTimes; i++){
            TimeUnit.MILLISECONDS.sleep(10);
        }
                                                                                                                   
        long end = System.currentTimeMillis();
                                                                                                                   
        System.out.println("Total time consumed by TimeUnit.MILLISECONDS.sleep : " + (end - start));
    }
                                                                                                               
    private static void sleepByThread(int sleepTimes) throws InterruptedException {
        long start = System.currentTimeMillis();
                                                                                                                   
        for(int i=0; i<sleepTimes; i++){
            Thread.sleep(10);
        }
                                                                                                                   
        long end = System.currentTimeMillis();
                                                                                                                   
        System.out.println("Total time consumed by Thread.sleep : " + (end - start));
    }
}
两次测试结果(Win7+4G+JDK7 测试期间计算资源充足):
Total time consumed by TimeUnit.MILLISECONDS.sleep : 100068
Total time consumed by Thread.sleep : 100134
Difference : -- -66
Total time consumed by TimeUnit.MILLISECONDS.sleep : 100222
Total time consumed by Thread.sleep : 100077
Difference : -- +145
从结果可以看出10000次调用差异很小,甚至一次更快,不排除JVM进行了优化,如果忽略性能方面考虑,从可读性方面建议使用TimeUnit枚举成员的sleep方法。
另外TimeUnit是枚举实现一个很好的实例,Doug Lea太神了,佩服得五体投地!

二、async的注意事项


1.@SpringBootApplication启动类当中添加@EnableAsync注解。
2.异步方法使用注解@Async的返回值只能为void或者Future。
3.没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理。

针对第三种情况的解决方法。
1.注解的方法必须是public方法。
2.方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
3.如果需要从类的内部调用,需要先获取其代理类。

以下内容选取自:@Async注解失效可能产生的原因及解决方案_Tang.Mr的博客-CSDN博客_async注解失效

1.@Async注解失效可能产生的原因及解决方案

1.1.未开启异步配置

需要在SpringBoot启动类上添加@EnableAsync注解

@SpringBootApplication
@EnableAsync//开启异步线程配置
public class AsyncDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
    }
}

或者在异步线程池配置类中添加@EnableAsync

@Slf4j
@EnableAsync
@Configuration
public class AsyncExecutorConfig implements AsyncConfigurer {

    @Value("${spring.async-executors.core-size}")
    private String CORE_SIZE;

    @Value("${spring.async-executors.max-size}")
    private String MAX_SIZE;

    @Value("${spring.async-executors.queue-size}")
    private String QUEUE_SIZE;

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Integer.parseInt(CORE_SIZE));
        executor.setMaxPoolSize(Integer.parseInt(MAX_SIZE));
        executor.setQueueCapacity(Integer.parseInt(QUEUE_SIZE));
        executor.setThreadNamePrefix("Spring Async Executor-");
        executor.setRejectedExecutionHandler(
                (runnable, threadPoolExecutor) -> {
                    try {
                        threadPoolExecutor.getQueue().put(runnable);
                    } catch (InterruptedException e) {
                        log.info("Thread pool receives InterruptedException: " + e);
                    }
                });
        executor.initialize();
        return executor;
    }
}
server:
  port: 9081
spring:
  async-executors:
    core-size: 100
    max-size: 200
    queue-size: 500

1.2.异步方法和调用者方法在同一个内中

前置条件开启了异步注解:

@Slf4j
@SpringBootTest
class AsyncDemoApplicationTests {

    @Test
    void contextLoads() {
        for (int i = 0; i < 10; i++) {
            testMethod(i);
        }
    }
    
    @Async
    public void testMethod(Integer integer){
        log.info("传入的参数为:"+integer);
    }

}

运行测试方法后输出的结果:

2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:0
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:1
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:2
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:3
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:4
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:5
2021-05-11 15:42:40.544  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:6
2021-05-11 15:42:40.545  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:7
2021-05-11 15:42:40.545  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:8
2021-05-11 15:42:40.545  INFO 16020 --- [           main] com.tangling.AsyncDemoApplicationTests   : 传入的参数为:9

从输出结果就可以看出来异步注解没有生效,如果异步注解生效的话他输入的数字应该是乱序的。

1.3.为什么异步方法与调用者在同一个内中会失效

@Transaction,@Async在同一个类中注解失效的原因和解决办法_baisq2017的博客-CSDN博客_@async不起作用

原因是:spring 在扫描bean的时候会扫描方法上是否包含@Async注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就没有增加异步作用,我们看到的现象就是@Async注解无效。
1.4.解决办法

将异步方法按照业务统一抽取到对应的bean中,当外部需要使用时将该bean注入,然后调用bean中的异步方法

上述代码改写:异步方法工具类

@Slf4j
@Component
public class AsyncUtils {
    @Async
    public void testMethod(Integer integer){
        log.info("传入的参数为:"+integer);
    }
}

测试类

@Slf4j
@SpringBootTest
class AsyncDemoApplicationTests {

    @Autowired
    private AsyncUtils asyncUtils;

    @Test
    void contextLoads() {
        for (int i = 0; i < 10; i++) {
            asyncUtils.testMethod(i);
        }
    }
}

运行输出结果

2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-1] com.tangling.utils.AsyncUtils            : 传入的参数为:0
2021-05-11 15:56:11.624  INFO 18424 --- [ync Executor-10] com.tangling.utils.AsyncUtils            : 传入的参数为:9
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-8] com.tangling.utils.AsyncUtils            : 传入的参数为:7
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-9] com.tangling.utils.AsyncUtils            : 传入的参数为:8
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-6] com.tangling.utils.AsyncUtils            : 传入的参数为:5
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-5] com.tangling.utils.AsyncUtils            : 传入的参数为:4
2021-05-11 15:56:11.625  INFO 18424 --- [sync Executor-3] com.tangling.utils.AsyncUtils            : 传入的参数为:2
2021-05-11 15:56:11.625  INFO 18424 --- [sync Executor-2] com.tangling.utils.AsyncUtils            : 传入的参数为:1
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-4] com.tangling.utils.AsyncUtils            : 传入的参数为:3
2021-05-11 15:56:11.624  INFO 18424 --- [sync Executor-7] com.tangling.utils.AsyncUtils            : 传入的参数为:6

三、传统异步处理

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//创建单个线程
private static ExecutorService executorService = Executors.newSingLeThreadExecutor();
executorService.execute(()->{……});
//创建线程池,固定50个线程,用于各种异步场景
private static ExecutorService pool = Executors.newFixedThreadPool(50);
pool.execute(()->{……});

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值