詳解Java中異步轉(zhuǎn)同步的六種方法
一、問(wèn)題
應(yīng)用場(chǎng)景
應(yīng)用中通過(guò)框架發(fā)送異步命令時(shí),不能立刻返回命令的執(zhí)行結(jié)果,而是異步返回命令的執(zhí)行結(jié)果。
那么,問(wèn)題來(lái)了,針對(duì)應(yīng)用中這種異步調(diào)用,能不能像同步調(diào)用一樣立刻獲取到命令的執(zhí)行結(jié)果,如何實(shí)現(xiàn)異步轉(zhuǎn)同步?
二、分析
首先,解釋下同步和異步
- 同步,就是發(fā)出一個(gè)調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回或繼續(xù)執(zhí)行后續(xù)操作。
- 異步,當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者在沒(méi)有得到結(jié)果之前,就可以繼續(xù)執(zhí)行后續(xù)操作。當(dāng)這個(gè)調(diào)用完成后,一般通過(guò)狀態(tài)、通知和回調(diào)來(lái)通知調(diào)用者。
對(duì)于異步調(diào)用,調(diào)用的返回并不受調(diào)用者控制。
異步轉(zhuǎn)同步主要實(shí)現(xiàn)思路:所有實(shí)現(xiàn)原理類似,是在發(fā)出調(diào)用的線程中進(jìn)行阻塞等待結(jié)果,調(diào)用完成后通過(guò)回調(diào)、設(shè)置共享狀態(tài)或通知進(jìn)行阻塞狀態(tài)的解除,繼續(xù)執(zhí)行后續(xù)操作。
三、實(shí)現(xiàn)方法
通常,實(shí)現(xiàn)中,不會(huì)無(wú)限的等待,一般會(huì)設(shè)定一個(gè)超時(shí)時(shí)間,具體超時(shí)時(shí)間根據(jù)具體場(chǎng)景確定。
下面以回調(diào)的方式介紹幾種常用實(shí)現(xiàn)異步轉(zhuǎn)同步的方法:
1.輪詢與休眠重試機(jī)制
采用輪詢與休眠重試機(jī)制,線程將反復(fù)在休眠和測(cè)試狀態(tài)條件中之間切換,直到超時(shí)或者狀態(tài)條件滿足繼續(xù)向下執(zhí)行。這種方式,超時(shí)時(shí)間控制不準(zhǔn)確,sleep時(shí)間需要在響應(yīng)性和CPU使用率之間進(jìn)行權(quán)衡。
private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待時(shí)間 5分鐘
private final Object lock = new Object();
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
synchronized(lock){
//設(shè)置狀態(tài)條件
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;
long remaining = MILLIS_OF_WAIT_TIME;//剩余等待時(shí)間
while(remaining > 0){
synchronized(lock){
if(狀態(tài)條件未滿足){
remaining = future - System.currentTimeMillis();
Thread.sleep(時(shí)間具體場(chǎng)景確定);
}
}
````}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}
2.wait/notify
任意一個(gè)Java對(duì)象,都擁有一組監(jiān)視器方法(wait、notify、notifyAll等方法),這些方法和synchronized同步關(guān)鍵字配合,可以實(shí)現(xiàn)等待/通知模式。但是使用wait/notify,使線程的阻塞/喚醒對(duì)線程本身來(lái)說(shuō)是被動(dòng)的,要準(zhǔn)確的控制哪個(gè)線程是很困難的,所以是要么隨機(jī)喚醒等待在條件隊(duì)列上一個(gè)線程(notify),要么喚醒所有的(notifyAll,但是很低效)。當(dāng)多個(gè)線程基于不同條件在同一條件隊(duì)列上等待時(shí),如果使用notify而不是notifyAll,很容易導(dǎo)致信號(hào)丟失的問(wèn)題,所以必須謹(jǐn)慎使用wait/notify方法。
private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待時(shí)間 5分鐘
private final Object lock = new Object();
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
synchronized(lock){
lock.notifyAll();
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;
long remaining = MILLIS_OF_WAIT_TIME;//剩余等待時(shí)間
synchronized(lock){
while(條件未滿足 && remaining > 0){ //被通知后要檢查條件
lock.wait(remaining);
remaining = future - System.currentTimeMillis();
}
````}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}
3.Lock Condition
使用Lock的Condition隊(duì)列的實(shí)現(xiàn)方式和wait/notify方式類似,但是Lock支持多個(gè)Condition隊(duì)列,并且支持等待狀態(tài)中響應(yīng)中斷。
private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時(shí)間 5分鐘
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
lock.lock();//這是前提
try {
condition.signal();
}finally {
lock.unlock();
}
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
lock.lock();//這是前提
try {
condition.await();
} catch (InterruptedException e) {
//TODO
}finally {
lock.unlock();
}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}
4.CountDownLatch
使用CountDownLatch可以實(shí)現(xiàn)異步轉(zhuǎn)同步,它好比計(jì)數(shù)器,在創(chuàng)建實(shí)例CountDownLatch對(duì)象的時(shí)候傳入數(shù)字,每使用一次 countDown() 方法計(jì)數(shù)減1,當(dāng)數(shù)字減到0時(shí), await()方法后的代碼將可以執(zhí)行,未到0之前將一直阻塞等待。
private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時(shí)間 5分鐘
private final CountDownLatch countDownLatch = new CountDownLatch(1);
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
countDownLatch.countDown();
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
try {
countDownLatch.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
//TODO
}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}
5.CyclicBarrier
讓一組線程達(dá)到一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到等待最后一個(gè)線程到達(dá)屏障時(shí),屏障才開門,所有被屏障攔截的線程才會(huì)繼續(xù)執(zhí)行。
每個(gè)線程通過(guò)調(diào)用await方法告訴CyclicBarrier我已經(jīng)到達(dá)了屏障,然后當(dāng)前的的線程被阻塞。
private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時(shí)間 5分鐘
private final CountDownLatch cyclicBarrier= new CyclicBarrier(2);//設(shè)置屏障攔截的線程數(shù)為2
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
//我也到達(dá)屏障了,可以開門了
cyclicBarrier.await();
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
try {
//我到達(dá)屏障了,還沒(méi)開門,要等一等
cyclicBarrier.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
//TODO
}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}
CountDownLatch和CyclicBarrier實(shí)現(xiàn)類似,區(qū)別是CountDownLatch的計(jì)數(shù)器只能使用一次,而CyclicBarrier的計(jì)數(shù)器可以使用reset重置,
所以CyclicBarrier能處理更為復(fù)雜的業(yè)務(wù)場(chǎng)景。在異步轉(zhuǎn)同步中,計(jì)數(shù)器不會(huì)重用,所以使用CountDownLatch實(shí)現(xiàn)更適合。
6.LockSupport
LockSupport定義了一組公共靜態(tài)方法,提供了最基本的線程阻塞和喚醒的方法。
private static long NANOS_OF_WAIT_TIME = 300000000L;// 等待時(shí)間 5分鐘
private final LockSupport lockSupport = new LockSupport();
//3.結(jié)果返回后進(jìn)行回調(diào),解除阻塞
@Override
public void callback(AsynResponse response){
lockSupport.unpark();
}
public Result getResult() throws ErrorCodeException {
// 1.異步調(diào)用
// 2.阻塞等待異步響應(yīng)
try {
lockSupport.parkNanos(NANOS_OF_WAIT_TIME);
} catch (InterruptedException e) {
//TODO
}
//4.超時(shí)或結(jié)果正確返回,對(duì)結(jié)果進(jìn)行處理
return result;
}到此這篇關(guān)于詳解Java中異步轉(zhuǎn)同步的六種方法的文章就介紹到這了,更多相關(guān)Java異步轉(zhuǎn)同步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于MD5算法原理與常用實(shí)現(xiàn)方式
這篇文章主要介紹了關(guān)于MD5算法原理與常用實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
Springboot中攔截GET請(qǐng)求獲取請(qǐng)求參數(shù)驗(yàn)證合法性核心方法
這篇文章主要介紹了Springboot中攔截GET請(qǐng)求獲取請(qǐng)求參數(shù)驗(yàn)證合法性,在Springboot中創(chuàng)建攔截器攔截所有GET類型請(qǐng)求,獲取請(qǐng)求參數(shù)驗(yàn)證內(nèi)容合法性防止SQL注入,這種方法適用攔截get類型請(qǐng)求,需要的朋友可以參考下2023-08-08
Java對(duì)象布局(JOL)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Java對(duì)象布局(JOL)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
springboot優(yōu)雅獲取前端參數(shù)的方法詳解
現(xiàn)在的項(xiàng)目基本上都是前后端分離的項(xiàng)目,如何打通前后端,接收前端傳過(guò)來(lái)的參數(shù)呢,這篇文章小編就來(lái)和大家詳細(xì)介紹一下springboot如何優(yōu)雅的獲取前端參數(shù)吧2024-03-03
沒(méi)有編輯器的環(huán)境下是如何創(chuàng)建Servlet(Tomcat+Java)項(xiàng)目的?
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著在沒(méi)有編輯器的環(huán)境下如何創(chuàng)建Servlet(Tomcat+Java)項(xiàng)目展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
SpringBoot統(tǒng)計(jì)接口調(diào)用耗時(shí)的三種方式
在實(shí)際開發(fā)中,了解項(xiàng)目中接口的響應(yīng)時(shí)間是必不可少的事情,SpringBoot 項(xiàng)目支持監(jiān)聽接口的功能也不止一個(gè),接下來(lái)我們分別以 AOP、ApplicationListener、Tomcat 三個(gè)方面去實(shí)現(xiàn)三種不同的監(jiān)聽接口響應(yīng)時(shí)間的操作,需要的朋友可以參考下2024-06-06

