多线程定时任务

不知道大家有没有这样的面试经历:
面试官:了解多线程吗?
答:这个不太了解
面试官:
在这里插入图片描述

多线程大家在初学的时候,对这个知识点应该有不少的疑惑的。我认为主要原因有两个:

多线程在初学的时候不太好学,并且一般写项目的时候也很少用得上(至少在初学阶段时写的项目基本不需要自己创建线程)。
多线程的知识点在面试经常考,多线程所涉及的知识点非常多,难度也不低。
这就会给人带来一种感觉「这破玩意涉及的东西是真的广,平时也不怎么用,怎么面试就偏偏爱问这个鬼东西」
最近面试刚好聊到了多线程的知识,由于在项目中实际使用场景很多,回答面试官的问题时如丝般顺滑,在此记录一下。

为什么使用多线程?

首先,我们要明确的是「为什么要使用多线程」,可能有人会认为「使用多线程就是为了加快程序运行的速度啊」。如果你是这样回答了,那面试官可能会问你「那多线程是怎么加快程序运行速度的?」

于我的理解:使用多线程最主要的原因是提高系统的资源利用率。

现在CPU基本都是多核的,如果你只用单线程,那就是只用到了一个核心,其他的核心就相当于空闲在那里了。我们要做的应该是充分发挥计算机多核CPU的优势,将串行的任务改为并行执行,提高执行效率。

多线程有哪些具体的业务场景?

经典场景:
火车站多个窗口卖票。参见:

https://blog.csdn.net/tomcat_2014/article/details/60575942

多线程离我们远吗?

其实在我们的日常开发中,也有很多的业务场景可以使用到多线程:
1、用户注册完成送大礼包/积分之类,且积分等也是另一个系统并比较耗时;且这类任务即使失败也不是特别重要的。
2、后台线程:比如定期执行一些特殊任务,如定期更新配置文件,任务调度(如quartz),一些监控用于定期信息采集等,笔者在这里也会分享后台的多线程定时任务。
3.Tomcat我相信每个Java后端的同学都认识它,它就是以多线程去响应请求的,我们可以在server.xml中配置连接池的配置,比如:

<Connector port="8080" maxThreads="350" maxHttpHeaderSize="8192" minSpareThreads="45" maxPostSize="512000" protocol="HTTP/1.1" enableLookups="false" redirectPort="8443" acceptCount="200" keepAliveTimeout="15000" maxKeepAliveRequests="-1" maxConnections="25000" connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI="true" URIEncoding="UTF-8" />

Tomcat处理每一个请求都会从线程连接池里边用一个线程去处理,这显然是多线程的操作。只是框架屏蔽了底层的细节访问。
还有我们在连接数据库的时候,也会用对应的连接池(Druid、C3P0、DBCP等),比如常见的Druid配置。
从上面总结下来,我们可以发现:我们日常「关于多线程的代码」写得不多,但是我们写的程序代码的的确确是在多线程的环境下跑的。

如果我们不懂多线程知识,很直接的一个现实:

在这里插入图片描述

虽然在工作中未必会全部用得上,但如果项目真的用到了,我们如果学过了可能就可以很快地理解当时为什么要这样设计(我觉得去挖掘过程还是挺有意思的)。

「我可能不用,但你必须要有」

这个道理也很容易懂:「我买电脑的时候,虽然我是木耳听不出什么音质出来,但你音质就是得好」。企业招人的时候也一样「你在工作的时候未必要写,但你必须要会」

至少在我看来,从求职的角度触发,多线程是很重要的。
我们开始动手吧:

多线程的实现方式

1、继承Thread类,重写run()方法;

2、实现Runnable接口,实现run()方法

3、实现Callable接口,实现call()方法;

4、通过线程池创建线程

由于前面三种方式实现线程网上的资料已经太多,笔者在这里提供采用第4种方式的分享


/**
 * @Description 线程池配置  定时任务,异步任务
 * @Author Chris he
 * @Date 2020/6/24 11:36
 * @Version V1.0
 **/
@Configuration  //配置自定义线程池
@EnableScheduling   //启用多线程定时任务
@EnableAsync    //异步执行
@Slf4j
public class ExecutorConfig implements SchedulingConfigurer, AsyncConfigurer {

    private Logger log = LoggerFactory.getLogger(ExecutorConfig.class);
    /**
     * 定时任务使用的线程池
     * @return
     */
    @Bean(destroyMethod = "shutdown", name = "taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("task-");
        scheduler.setAwaitTerminationSeconds(600);
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        return scheduler;
    }

    /**
     * 异步任务执行线程池
     * @return
     */
    @Bean(name = "asyncExecutor")
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setQueueCapacity(1000);
        executor.setKeepAliveSeconds(600);
        executor.setMaxPoolSize(20);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = taskScheduler();
        scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
    }

    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects);
        };
    }

}


/**
 * @Author: Chris he
 * @Date: 2020/6/23 14:10
 * 多线程执行定时任务
 */
@Component
@EnableScheduling
public class SaticScheduleTask {

    private Logger logger = LoggerFactory.getLogger(SaticScheduleTask.class);

    /**
     * 第一个定时任务
     * @throws InterruptedException
     */
    @Async
    @Scheduled(cron = "0 */1 * * * ?")//每分钟
    public void first() throws InterruptedException {
        logger.info("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime()+ "。线程 : " + Thread.currentThread().getName());
    //TODO 执行业务
    }

    /**
     * 第二个定时任务
     */
    @Async
    @Scheduled(cron = "0 */1 * * * ?")//每分钟
    public void second() {
        logger.info("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime()+ "。线程 : " + Thread.currentThread().getName());
       //TODO 执行业务
    }


}

使用线程池的好处是什么?

如果多个任务同时触发,那可能某些任务执行时间过长,请求可能会被阻塞住,而我们如果放在线程池中可以提高系统的吞吐量。

使用线程池的时候,往往我们的调用方都不需要考虑请求是否立马处理成功。假设线程池在处理任务的时候因为某些原因失败了,我们可以走报警机制(用邮件/短信等渠道去提醒请求方即可)。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/chrishe751/article/details/107552719