若是一個項目總用單線程來跑,不免會遇到一些性能問題,因此再開發中,咱們應該儘可能適量的使用多線程(在保證線程安全的狀況下)。java
本教程大概目錄:nginx
/**
* Created by Fant.J.
*/
@RestController
@Slf4j
public class AsyncController {
/**
* 單線程測試
* @return
* @throws InterruptedException
*/
@RequestMapping("/order")
public String order() throws InterruptedException {
log.info("主線程開始");
Thread.sleep(1000);
log.info("主線程返回");
return "success";
}
}
複製代碼
咱們把線程休息一秒看成模擬處理業務所花費的時間。很明顯能看出來,這是個單線程。 redis
nio-8080-exec-1
表示主線程的線程1。
/**
* 用Callable實現異步
* @return
* @throws InterruptedException
*/
@RequestMapping("/orderAsync")
public Callable orderAsync() throws InterruptedException {
log.info("主線程開始");
Callable result = new Callable() {
@Override
public Object call() throws Exception {
log.info("副線程開始");
Thread.sleep(1000);
log.info("副線程返回");
return "success";
}
};
log.info("主線程返回");
return result;
}
複製代碼
咱們能夠看到,主線程的開始和返回(結束處理)是首先執行的,而後副線程才執行真正的業務處理。說明主線程在這裏的做用是調用(喚醒)子線程,子線程處理完會返回一個Object對象,而後返回給用戶。spring
這樣雖然實現了併發處理,可是有一個問題,就是主線程和副線程沒有作到徹底分離,畢竟是一個嵌套進去的副線程。編程
因此爲了優化咱們的實現,我在這裏模擬 消息中間件 來實現主線程副線程的徹底分離。安全
由於本章主要講的是併發編程原理,因此這裏咱們不用現成的消息隊列來搞,咱們模擬一個消息隊列來處理。springboot
/**
* 模擬消息隊列 類
* Created by Fant.J.
*/
@Component
@Slf4j
public class MockQueue {
//下單消息
private String placeOrder;
//訂單完成消息
private String completeOrder;
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) throws InterruptedException {
new Thread(()->{
log.info("接到下單請求"+placeOrder);
//模擬處理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//給completeOrder賦值
this.completeOrder = placeOrder;
log.info("下單請求處理完畢"+placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
複製代碼
注意再setPlaceOrder(String placeOrder)方法裏,我建立了一個新的線程來處理接單的操做(爲何要創建新線程,怕主線程在這掛起,此段邏輯也沒有線程安全問題,何況異步處理更快)。傳進來的參數是個 訂單號 ,通過1s的處理成功後,把訂單號傳給completeOrder 字段,說明用戶下單成功,我在下面付controller調用該方法的代碼bash
//注入模擬消息隊列類
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
....
@RequestMapping("/orderMockQueue")
public DeferredResult orderQueue() throws InterruptedException {
log.info("主線程開始");
//隨機生成8位數
String orderNumber = RandomStringUtils.randomNumeric(8);
mockQueue.setPlaceOrder(orderNumber);
DeferredResult result = new DeferredResult();
deferredResultHolder.getMap().put(orderNumber,result);
Thread.sleep(1000);
log.info("主線程返回");
return result;
}
複製代碼
好了,而後咱們還須要一箇中介類來存放訂單號和處理結果。爲何須要這麼一個類呢,由於咱們以前說過要實現主線程和副線程分離,因此須要一箇中介來存放處理信息(好比:這個訂單號信息,和處理結果信息),咱們判斷處理結果是否爲空就知道該副線程執行了沒有。因此咱們寫一箇中介類DeferredResultHolder 。多線程
######DeferredResultHolder .java併發
/**
* 訂單處理狀況 中介/持有者
* Created by Fant.J.
*/
@Component
public class DeferredResultHolder {
/**
* String: 訂單號
* DeferredResult:處理結果
*/
private Map<String,DeferredResult> map = new HashMap<>();
public Map<String, DeferredResult> getMap() {
return map;
}
public void setMap(Map<String, DeferredResult> map) {
this.map = map;
}
}
複製代碼
在重複一次-.-,爲何須要這麼一個類呢,由於咱們以前說過要實現主線程和副線程分離,因此須要一箇中介來存放處理信息(好比:這個訂單號信息,和處理結果信息),一個訂單確定要對應一個結果。否則豈不是亂了套。
DeferredResult是用來放處理結果的對象。
好了,那新問題又來了,咱們怎麼去判斷訂單處理成功了沒有,咱們此時就須要寫一個監聽器,過100毫秒監聽一次MockQueue類中的completeOrder中是否有值,若是有值,那麼這個訂單就須要被處理。咱們寫一個監聽器。
/**
* Queue監聽器
* Created by Fant.J.
*/
@Component
@Slf4j
public class QueueListener implements ApplicationListener<ContextRefreshedEvent>{
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
new Thread(()->{
while(true){
//判斷CompleteOrder字段是不是空
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())){
String orderNumber = mockQueue.getCompleteOrder();
deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
log.info("返回訂單處理結果");
//將CompleteOrder設爲空,表示處理成功
mockQueue.setCompleteOrder(null);
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
複製代碼
分割線後,我再給你們帶來一批乾貨,自定義線程池 https://www.jianshu.com/p/832f2b162450
學完這個後,再看下面的。。
咱們前面的代碼中,有兩部分有用new Thread()來建立線程,咱們有本身的線程池後,就能夠用線程池來分配線程任務了,我在自定義線程裏有講,我用的是第二種配置方法(用@Async註解來給線程 )。 修改以下:
@Async
public void setPlaceOrder(String placeOrder) throws InterruptedException {
log.info("接到下單請求"+placeOrder);
//模擬處理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//給completeOrder賦值
this.completeOrder = placeOrder;
log.info("下單請求處理完畢"+placeOrder);
}
複製代碼
咱們看看效果:
圈紅圈的就是咱們本身定義的線程池裏分配的線程。
謝謝你們!