1.同步、异步
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而,异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作。
举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
2.阻塞、非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
还是上面的例子:
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
阻塞和非阻塞,应该描述的是一种状态,同步与非同步描述的是行为方式
spring中使用@Async注解进行异步处理
一、原理
spring 在扫描bean的时候会扫描方法上是否包含@async的注解,如果包含的,spring会为这个bean动态的生成一个子类,我们称之为代理类(?), 代理类是继承我们所写的bean的,然后把代理类注入进来,那此时,在执行此方法的时候,会到代理类中,代理类判断了此方法需要异步执行,就不会调用父类 (我们原本写的bean)的对应方法。spring自己维护了一个队列,他会把需要执行的方法,放入队列中,等待线程池去读取这个队列,完成方法的执行, 从而完成了异步的功能。
我们可以关注到在配置task的时候,是有参数让我们配置线程池的数量的。因为这种实现方法,所以在同一个类中的方法调用,添加@async注解是失效的!,原因是当你在同一个类中的时候,方法调用是在类体内执行的,spring无法截获这个方法调用。详因见此链接:https://blog.csdn.net/clementad/article/details/47339519
二、配置
task:annotation-driven 配置:
executor:指定一个缺省的executor给@Async使用。
例子:task:annotation-driven executor=”asyncExecutor”
task:executor 配置:
- id:当配置多个executor时,被@Async(“id”)指定使用;也被作为线程名的前缀。
- pool-size:
- core size:最小的线程数,缺省:1
- max size:最大的线程数,缺省:Integer.MAX_VALUE
- queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
- keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
- rejection-policy:当pool已经达到max size的时候,如何处理新任务
- - ABORT(缺省):抛出TaskRejectedException异常,然后不执行
- - DISCARD:不执行,也不抛出异常
- - DISCARD_OLDEST:丢弃queue中最旧的那个任务
- - CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
例子:
task:annotation-driven executor=”asyncExecutor”
task:executor id=”asyncExecutor” pool-size=”100-10000” queue-capacity=”10”
<!-- 缺省的异步任务线程池 -->
<task:annotation-driven executor="asyncExecutor" />
<task:executor id="asyncExecutor" pool-size="100-10000" queue-capacity="10" />
<!-- 处理log的线程池 -->
<task:executor id="logExecutor" pool-size="15-1000" queue-capacity="5" keep-alive="5"/>
@Override
@Async("logExecutor") //如果不指定名字,会使用缺省的“asyncExecutor”
public void saveUserOpLog(TabUserOpLog tabUserOpLog) {
userOpLogDAO.insertTabUserOpLog(tabUserOpLog);
}
三、两种情况:无返回值和有返回值
1.无返回值
@Component
public class TestAsyncBean {
@Async
public void sayHello3() throws InterruptedException {
Thread.sleep(2 * 1000);//网络连接中 。。。消息发送中。。。
System.out.println("我爱你啊!");
}
}
2.有返回值
通过直接获取返回值得方式是不行的,这里就需要用到异步回调,异步方法返回值必须为Future<>,就像Callable与Future。
+ View code
@Component
public class TestAsyncBean {
@Async
public Future<String> sayHello1() throws InterruptedException {
int thinking = 2;
Thread.sleep(thinking * 1000);//网络连接中 。。。消息发送中。。。
System.out.println("我爱你啊!");
return new AsyncResult<String>("发送消息用了"+thinking+"秒");
}
}
以上示例可以发现,返回的数据类型为Future类型,其为一个接口。具体的结果类型为AsyncResult,这个是需要注意的地方。
主要参考链接:
https://www.zhihu.com/question/19732473/answer/20851256
https://blog.csdn.net/clementad/article/details/47403185
https://www.cnblogs.com/zhengbin/p/6104502.html