java中使用异步编程方式

工作中常会为了提高代码执行效率,采用异步编程的方式,这里介绍几种异步编程的实现方式

1. jdk1.8之前的Future

jdk并发包里的Future代表了未来的某个结果,当我们向线程池中提交任务的时候会返回该对象,可以通过future获得执行的结果,但是jdk1.8之前的Future有点鸡肋,并不能实现真正的异步,需要阻塞的获取结果,或者不断的轮询。

通常我们希望当线程执行完一些耗时的任务后,能够自动的通知我们结果,很遗憾这在原生jdk1.8之前是不支持的,但是我们可以通过第三方的库实现真正的异步回调。

/**
 * jdk1.8之前的Future
 * @author Administrator
 */
public class JavaFuture {
	public static void main(String[] args) throws Throwable, ExecutionException {
		ExecutorService executor = Executors.newFixedThreadPool(1);
		Future<String> f = executor.submit(new Callable<String>() {
 
			@Override
			public String call() throws Exception {
				System.out.println("task started!");
				longTimeMethod();
				System.out.println("task finished!");
				return "hello";
			}
		});
 
		//此处get()方法阻塞main线程
		System.out.println(f.get());
		System.out.println("main thread is blocked");
	}
}

 

如果想获得耗时操作的结果,可以通过get()方法获取,但是该方法会阻塞当前线程,我们可以在做完剩下的某些工作的时候调用get()方法试图去获取结果。

也可以调用非阻塞的方法isDone来确定操作是否完成,isDone这种方式有点儿类似下面的过程:
在这里插入图片描述

2. jdk1.8开始的Future

直到jdk1.8才算真正支持了异步操作,jdk1.8中提供了lambda表达式,使得java向函数式语言又靠近了一步。借助jdk原生的CompletableFuture可以实现异步的操作,同时结合lambada表达式大大简化了代码量。代码例子如下:

package netty_promise;
 
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
 
/**
 * 基于jdk1.8实现任务异步处理
 * @author Administrator
 */
public class JavaPromise {
	public static void main(String[] args) throws Throwable, ExecutionException {
		// 两个线程的线程池
		ExecutorService executor = Executors.newFixedThreadPool(2);
		//jdk1.8之前的实现方式
		CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
			@Override
			public String get() {
				System.out.println("task started!");
				try {
					//模拟耗时操作
					longTimeMethod();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return "task finished!";
			}
		}, executor);
 
		//采用lambada的实现方式
		future.thenAccept(e -> System.out.println(e + " ok"));
		
		System.out.println("main thread is running");
	}
}

 

实现方式类似下图:
在这里插入图片描述

3.采用Spring 的异步方法去执行(无返回值)

在启动类或者配置类加上 @EnableAsync 注解.

package me.deweixu.aysncdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
public class AysncDemoApplication {

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

先把longTimeMethod 封装到Spring的异步方法中,这个方法一定要写在Spring管理的类中,注意注解@Async

@Async注解可以用在方法上,也可以用在类上,用在类上,对类里面所有方法起作用

@Service
public class AsynchronousService{
  @Async
  public void springAsynchronousMethod(){
    longTimeMethod();
  }
}

其他类调用这个方法。这里注意,一定要其他的类,如果在同类中调用,是不生效的。具体原因,可以去学习一下Spring AOP的原理

@Autowired
private AsynchronousService asynchronousService;

public void useAsynchronousMethod(){
  //我们需要执行的代码1
  asynchronousService.springAsynchronousMethod();
 //我们需要执行的代码2
}
 

 

4.采用Spring 的异步方法+Future接收返回值

分析: 这些获取异步方法的结果信息,是通过不停的检查Future的状态来获取当前的异步方法是否执行完毕来实现的。

先把longTimeMethod 封装到Spring的异步方法中,这个异步方法的返回值是Future的实例。这个方法一定要写在Spring管理的类中,注意注解@Async。

@Service
public class AsynchronousService{
  @Async
  public Future springAsynchronousMethod(){
    Integer result = longTimeMethod();
    return new AsyncResult(result);
  }
}

其他类调用这个方法。这里注意,一定要其他的类,如果在同类中调用,是不生效的。

如果调用之后接收返回值,不对返回值进行操作则为异步操作,进行操作则转为同步操作,等待对返回值操作完之后,才会继续执行主进程下面的流程

@Autowired
private AsynchronousService asynchronousService;

public void useAsynchronousMethod(){
    Future future = asynchronousService.springAsynchronousMethod();
    future.get(1000, TimeUnit.MILLISECONDS);
}

5、spring中@Async需要注意的点

1、@Async可以标注在类上(所有都是同步方法)/方法上。

2、被标注了@Async的方法表示别人在调用这个方法时是用异步的方式进行的。注意不能在本类调用(本类中调用无效)

3、@Async和spring的声明式事务@Transactional,

 在@Async标注的方法,同时也适用了@Transactional进行了标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。

     那该如何给这些操作添加事务管理呢?可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.

    例如:  方法A,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。

          方法B,使用了@Async来标注,  B中调用了C、D,C/D分别使用@Transactional做了标注,则可实现事务控制的目的。

4、@Async默认使用SimpleAsyncTaskExecutor

该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。

所以我们需要自定义一个线程池:

@Async应用自定义线程池

    自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。自定义线程池有如下模式:

  • 重新实现接口AsyncConfigurer
  • 继承AsyncConfigurerSupport
  • 配置由自定义的TaskExecutor替代内置的任务执行器

   通过查看Spring源码关于@Async的默认调用规则,会优先查询源码中实现AsyncConfigurer这个接口的类,实现这个接口的类为AsyncConfigurerSupport。但默认配置的线程池和异步处理方法均为空,所以,无论是继承或者重新实现接口,都需指定一个线程池。且重新实现 public Executor getAsyncExecutor()方法。

  •  实现接口AsyncConfigurer

 1 @Configuration
 2 public class AsyncConfiguration implements AsyncConfigurer {
 3     @Bean("kingAsyncExecutor")
 4     public ThreadPoolTaskExecutor executor() {
 5         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 6         int corePoolSize = 10;
 7         executor.setCorePoolSize(corePoolSize);
 8         int maxPoolSize = 50;
 9         executor.setMaxPoolSize(maxPoolSize);
10         int queueCapacity = 10;
11         executor.setQueueCapacity(queueCapacity);
12         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
13         String threadNamePrefix = "kingDeeAsyncExecutor-";
14         executor.setThreadNamePrefix(threadNamePrefix);
15         executor.setWaitForTasksToCompleteOnShutdown(true);
16         // 使用自定义的跨线程的请求级别线程工厂类
17         RequestContextThreadFactory threadFactory = RequestContextThreadFactory.getDefault();
18         executor.setThreadFactory(threadFactory);
19         int awaitTerminationSeconds = 5;
20         executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
21         executor.initialize();
22         return executor;
23     }
24 
25     @Override
26     public Executor getAsyncExecutor() {
27         return executor();
28     }
29 
30     @Override
31     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
32         return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex);
33     }
34 }
  •  继承AsyncConfigurerSupport

@Configuration  
@EnableAsync  
class SpringAsyncConfigurer extends AsyncConfigurerSupport {  
  
    @Bean  
    public ThreadPoolTaskExecutor asyncExecutor() {  
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();  
        threadPool.setCorePoolSize(3);  
        threadPool.setMaxPoolSize(3);  
        threadPool.setWaitForTasksToCompleteOnShutdown(true);  
        threadPool.setAwaitTerminationSeconds(60 * 15);  
        return threadPool;  
    }  
  
    @Override  
    public Executor getAsyncExecutor() {  
        return asyncExecutor;  
}  

  @Override  
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return (ex, method, params) -> ErrorLogger.getInstance().log(String.format("执行异步任务'%s'", method), ex);
}
} 
  • 配置自定义的TaskExecutor

    由于AsyncConfigurer的默认线程池在源码中为空,Spring通过beanFactory.getBean(TaskExecutor.class)先查看是否有线程池,未配置时,通过beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class),又查询是否存在默认名称为TaskExecutor的线程池。所以可在项目中,定义名称为TaskExecutor的bean生成一个默认线程池。也可不指定线程池的名称,申明一个线程池,本身底层是基于TaskExecutor.class便可。

比如:

 Executor.class:ThreadPoolExecutorAdapter->ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor(这样的模式,最终底层为Executor.class,在替换默认的线程池时,需设置默认的线程池名称为TaskExecutor)
 TaskExecutor.class:ThreadPoolTaskExecutor->SchedulingTaskExecutor->AsyncTaskExecutor->TaskExecutor(这样的模式,最终底层为TaskExecutor.class,在替换默认的线程池时,可不指定线程池名称。)
 1 @EnableAsync
 2 @Configuration
 3 public class TaskPoolConfig {
 4     @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
 5     public Executor taskExecutor() {
 6         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 7          //核心线程池大小
 8         executor.setCorePoolSize(10);
 9         //最大线程数
10         executor.setMaxPoolSize(20);
11         //队列容量
12         executor.setQueueCapacity(200);
13         //活跃时间
14         executor.setKeepAliveSeconds(60);
15         //线程名字前缀
16         executor.setThreadNamePrefix("taskExecutor-");
17         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
18         return executor;
19     }
      @Bean(name = "new_task")
 5     public Executor taskExecutor() {
 6         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 7          //核心线程池大小
 8         executor.setCorePoolSize(10);
 9         //最大线程数
10         executor.setMaxPoolSize(20);
11         //队列容量
12         executor.setQueueCapacity(200);
13         //活跃时间
14         executor.setKeepAliveSeconds(60);
15         //线程名字前缀
16         executor.setThreadNamePrefix("taskExecutor-");
17         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
18         return executor;
19     }
20 }
  • 多个线程池

   @Async注解,使用系统默认或者自定义的线程池(代替默认线程池)。可在项目中设置多个线程池,在异步调用时,指明需要调用的线程池名称,如@Async("new_task")。

5、异常处理

https://www.cnblogs.com/toplist/p/7747225.html

6、Java如何将异步调用转为同步

换句话说,就是需要在异步调用过程中,持续阻塞至获得调用结果。

  • 使用wait和notify方法
  • 使用条件锁
  • Future
  • 使用CountDownLatch
  • 使用CyclicBarrier

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值