同步、异步(@Async);阻塞、非阻塞

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值