spring boot(五)异步调用

版权声明:版权没有,盗用不究 https://blog.csdn.net/liman65727/article/details/82116057

前言

spring boot中调用异步并没有想象中的那么复杂,之前在实习过程中已经遇到了几次异步调用的需求,自己都没能顺利弄下来,这里正好来一个总结

创建定时任务

有时候系统中需要定时做一些任务,实现定时任务也没有想象中的复杂(毕竟只是实现hello world)

1、创建定时任务实现类

package com.learn.springbootScheduled.util;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

	private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:SS");
	
	@Scheduled(fixedRate=5000)
	public void reportCurrentTime() {
		System.out.println("现在时间:"+dateFormat.format(new Date(System.currentTimeMillis())));
	}
	
}

标记Component注解,将类交给spring管理。在方法上打上@Scheduled标签,该注解常用有如下几个属性。

注解示例 作用
@Scheduled(fixedRate=5000) 上一次开始执行时间之后5秒再执行
@Scheduled(fixedDelay=5000) 上一次执行完毕时间点之后5秒再执行
@Scheduled(initialDelay=1000,fixedRate=5000)

第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次

@Scheduled(cron="*/5*****") 通过cron表达式定义规则

2、在主加载类上添加@EnableScheduling注解

@SpringBootApplication
@EnableScheduling
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

测试结果

 @Asyn实现异步

spring boot中实现异步操作也是非常容易的,比起之前什么的master-work模式,什么实现runnable接口,这个,已经非常简洁了,废话不多说,直接上示例,毕竟hello world级别的示例异常简单。

ps:关于同步和异步的区别,这里我就不解释了。

spring boot中我们只需要在方法上打上@Asyn标签就能实现异步操作。

异步调用(非回调)

1、编写异步逻辑实现类

package com.learn.springbootAsync.utils;

import java.util.Random;
import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment: 异步功能函数
 */
@Component
public class Task {

	public static Random random = new Random();

	@Async
	public void doTaskOne() throws InterruptedException {
		System.out.println("开始做任务一");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
	}

	@Async
	public void doTaskTwo() throws InterruptedException {
		System.out.println("开始做任务二");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
	}

	@Async
	public void doTaskThree() throws InterruptedException {
		System.out.println("开始做任务三");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
	}
}

2、主加载类中加上@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

3、测试代码

@Test
public void test() throws Exception {
	task.doTaskOne();
	task.doTaskTwo();
	task.doTaskThree();
	//主线程暂停
	Thread.sleep(1000);
}

测试结果:

每次执行,任务完成先后顺序会不同。

@Async注解不能修饰静态方法,否则不会生效。

异步回调

所谓的回调就是主线程需要获取异步线程的返回结果,这个详细内容后面需要参考Java 高并发的相关书籍(之前学习的,都他妈忘光了)。

1、回调线程类

package com.learn.springbootAsync.utils;

import java.util.Random;
import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment: 异步功能函数
 */
@Component
public class TaskFuture {

	public static Random random = new Random();

	@Async
	public Future<String> doTaskOne() throws InterruptedException {
		System.out.println("开始做任务一");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任务一完成");
	}

	@Async
	public Future<String> doTaskTwo() throws InterruptedException {
		System.out.println("开始做任务二");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任务二完成");
	}

	@Async
	public Future<String> doTaskThree() throws InterruptedException {
		System.out.println("开始做任务三");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任务三完成");
	}
}

2、主加载类中同样需要@EnableAsync注解

3、测试代码

	@Test
	public void testFuture() throws Exception {
		long start = System.currentTimeMillis();

		Future<String> task1 = taskFuture.doTaskOne();
		Future<String> task2 = taskFuture.doTaskTwo();
		Future<String> task3 = taskFuture.doTaskThree();

		while(true) {
			if(task1.isDone() && task2.isDone() && task3.isDone()) {
				// 三个任务都调用完成,退出循环等待
				System.out.println("回调线程已经全部执行结束");
				break;
			}
			
			System.out.println("主线程正在进行其他操作");
			Thread.sleep(1000);
		}
		long end = System.currentTimeMillis();
		System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
	}

测试结果:

其中的完成任务X语句就是线程回调的结果 ,耗时统计就是主线程完成的工作,根据这个模板,主线程在完成调用其他线程的时候,还可以完成自己的一些逻辑。

使用自定义的线程池

spring boot中使用自定义的线程池,关于线程池的介绍,建议参考《Java 并发编程实战》一书

1、首先在spring boot主类中定义一个线程池

package com.learn.springbootThreadPoolSelf;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
	
	@EnableAsync
	@Configuration
	class TaskPoolConfiguration{
		
		@Bean("taskExecutor")
		public Executor taskExecutor() {
			ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(200);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("taskExecutor-");
            executor.setAwaitTerminationSeconds(60);
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
		}
	}
}

这里一大堆相关线程池属性的设置,还是有必要了解的,在前面推荐的书籍中都有介绍,后面的博客会总结到这一步,这里暂时忽略相关属性。

2、@Async中指定线程池名称

package com.learn.springbootThreadPoolSelf.util;

import java.util.Random;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment:
 *
 */
@Component
public class TaskThreadPool {
	public static Random random = new Random();

    @Async("taskExecutor")
    public void doTaskOne() throws Exception {
    	System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任务一,耗时:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public void doTaskTwo() throws Exception {
    	System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任务二,耗时:" + (end - start) + "毫秒");
    }
    
    @Async("taskExecutor")
    public void doTaskThree() throws Exception {
    	System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

@Async中指定了之前设置的线程池名称。这些线程逻辑代码会自动加入到线程池中。

3、测试代码

@Test
public void test() throws Exception {

    task.doTaskOne();
    task.doTaskTwo();
    task.doTaskThree();

    Thread.currentThread().join();
    Thread.sleep(2000);
}

运行结果:

 线程名称为设置的线程名,说明任务已经放到指定的线程池中进行执行。

总结

总体来说,到目前为止,5篇spring boot系列的博客,都只是存在helloworld级别,对实际开发有一定参考,每篇博客都有些不足,对于一些稍微麻烦的示例,都没有进行深入实现。这五篇博客也是每天在工作之余完成的,没有更多的时间进行修饰。后续依旧会总结spring boot的其他功能。目前这5篇基本总结了spring boot中比较常用的功能。

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/82116057