1. 程式人生 > >同步、非同步(@Async);阻塞、非阻塞

同步、非同步(@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,這個是需要注意的地方。