Java異步調(diào)用轉(zhuǎn)同步的方法
先來說一下對異步和同步的理解:
同步調(diào)用:調(diào)用方在調(diào)用過程中,持續(xù)等待返回結(jié)果。
異步調(diào)用:調(diào)用方在調(diào)用過程中,不直接等待返回結(jié)果,而是執(zhí)行其他任務(wù),結(jié)果返回形式通常為回調(diào)函數(shù)。
其實(shí),兩者的區(qū)別還是很明顯的,這里也不再細(xì)說,我們主要來說一下Java如何將異步調(diào)用轉(zhuǎn)為同步。換句話說,就是需要在異步調(diào)用過程中,持續(xù)阻塞至獲得調(diào)用結(jié)果。
不賣關(guān)子,先列出五種方法,然后一一舉例說明:
- 使用wait和notify方法
- 使用條件鎖
- Future
- 使用CountDownLatch
- 使用CyclicBarrier
1.構(gòu)造一個(gè)異步調(diào)用
首先,寫demo需要先寫基礎(chǔ)設(shè)施,這里的話主要是需要構(gòu)造一個(gè)異步調(diào)用模型。異步調(diào)用類:
public class AsyncCall { private Random random = new Random(System.currentTimeMillis()); private ExecutorService tp = Executors.newSingleThreadExecutor(); //demo1,2,4,5調(diào)用方法 public void call(BaseDemo demo){ new Thread(()->{ long res = random.nextInt(10); try { Thread.sleep(res*1000); } catch (InterruptedException e) { e.printStackTrace(); } demo.callback(res); }).start(); } //demo3調(diào)用方法 public Future<Long> futureCall(){ return tp.submit(()-> { long res = random.nextInt(10); try { Thread.sleep(res*1000); } catch (InterruptedException e) { e.printStackTrace(); } return res; }); } public void shutdown(){ tp.shutdown(); } }
我們主要關(guān)心call方法,這個(gè)方法接收了一個(gè)demo參數(shù),并且開啟了一個(gè)線程,在線程中執(zhí)行具體的任務(wù),并利用demo的callback方法進(jìn)行回調(diào)函數(shù)的調(diào)用。大家注意到了這里的返回結(jié)果就是一個(gè)[0,10)的長整型,并且結(jié)果是幾,就讓線程sleep多久——這主要是為了更好地觀察實(shí)驗(yàn)結(jié)果,模擬異步調(diào)用過程中的處理時(shí)間。
至于futureCall和shutdown方法,以及線程池tp都是為了demo3利用Future來實(shí)現(xiàn)做準(zhǔn)備的。
demo的基類:
public abstract class BaseDemo { protected AsyncCall asyncCall = new AsyncCall(); public abstract void callback(long response); public void call(){ System.out.println("發(fā)起調(diào)用"); asyncCall.call(this); System.out.println("調(diào)用返回"); } }
BaseDemo非常簡單,里面包含一個(gè)異步調(diào)用類的實(shí)例,另外有一個(gè)call方法用于發(fā)起異步調(diào)用,當(dāng)然還有一個(gè)抽象方法callback需要每個(gè)demo去實(shí)現(xiàn)的——主要在回調(diào)中進(jìn)行相應(yīng)的處理來達(dá)到異步調(diào)用轉(zhuǎn)同步的目的。
2. 使用wait和notify方法
這個(gè)方法其實(shí)是利用了鎖機(jī)制,直接貼代碼:
public class Demo1 extends BaseDemo{ private final Object lock = new Object(); @Override public void callback(long response) { System.out.println("得到結(jié)果"); System.out.println(response); System.out.println("調(diào)用結(jié)束"); synchronized (lock) { lock.notifyAll(); } } public static void main(String[] args) { Demo1 demo1 = new Demo1(); demo1.call(); synchronized (demo1.lock){ try { demo1.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("主線程內(nèi)容"); } }
可以看到在發(fā)起調(diào)用后,主線程利用wait進(jìn)行阻塞,等待回調(diào)中調(diào)用notify或者notifyAll方法來進(jìn)行喚醒。注意,和大家認(rèn)知的一樣,這里wait和notify都是需要先獲得對象的鎖的。在主線程中最后我們打印了一個(gè)內(nèi)容,這也是用來驗(yàn)證實(shí)驗(yàn)結(jié)果的,如果沒有wait和notify,主線程內(nèi)容會緊隨調(diào)用內(nèi)容立刻打??;而像我們上面的代碼,主線程內(nèi)容會一直等待回調(diào)函數(shù)調(diào)用結(jié)束才會進(jìn)行打印。
沒有使用同步操作的情況下,打印結(jié)果:
發(fā)起調(diào)用 調(diào)用返回 主線程內(nèi)容 得到結(jié)果 1 調(diào)用結(jié)束
而使用了同步操作后:
發(fā)起調(diào)用 調(diào)用返回 得到結(jié)果 9 調(diào)用結(jié)束 主線程內(nèi)容
3. 使用條件鎖
和方法一的原理類似:
public class Demo2 extends BaseDemo { private final Lock lock = new ReentrantLock(); private final Condition con = lock.newCondition(); @Override public void callback(long response) { System.out.println("得到結(jié)果"); System.out.println(response); System.out.println("調(diào)用結(jié)束"); lock.lock(); try { con.signal(); }finally { lock.unlock(); } } public static void main(String[] args) { Demo2 demo2 = new Demo2(); demo2.call(); demo2.lock.lock(); try { demo2.con.await(); } catch (InterruptedException e) { e.printStackTrace(); }finally { demo2.lock.unlock(); } System.out.println("主線程內(nèi)容"); } }
基本上和方法一沒什么區(qū)別,只是這里使用了條件鎖,兩者的鎖機(jī)制有所不同。
4. Future
使用Future的方法和之前不太一樣,我們調(diào)用的異步方法也不一樣。
public class Demo3{ private AsyncCall asyncCall = new AsyncCall(); public Future<Long> call(){ Future<Long> future = asyncCall.futureCall(); asyncCall.shutdown(); return future; } public static void main(String[] args) { Demo3 demo3 = new Demo3(); System.out.println("發(fā)起調(diào)用"); Future<Long> future = demo3.call(); System.out.println("返回結(jié)果"); while (!future.isDone() && !future.isCancelled()); try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("主線程內(nèi)容"); } }
我們調(diào)用futureCall方法,方法中會想線程池tp提交一個(gè)Callable,然后返回一個(gè)Future,這個(gè)Future就是我們demo3中call中得到的,得到future對象之后就可以關(guān)閉線程池啦,調(diào)用asyncCall的shutdown方法。關(guān)于關(guān)閉線程池這里有一點(diǎn)需要注意,我們回過頭來看看asyncCall的shutdown方法:
public void shutdown(){ tp.shutdown(); }
發(fā)現(xiàn)只是簡單調(diào)用了線程池的shutdown方法,然后我們說注意的點(diǎn),這里最好不要用tp的shutdownNow方法,該方法會試圖去中斷線程中中正在執(zhí)行的任務(wù);也就是說,如果使用該方法,有可能我們的future所對應(yīng)的任務(wù)將被中斷,無法得到執(zhí)行結(jié)果。
然后我們關(guān)注主線程中的內(nèi)容,主線程的阻塞由我們自己來實(shí)現(xiàn),通過future的isDone和isCancelled來判斷執(zhí)行狀態(tài),一直到執(zhí)行完成或被取消。隨后,我們打印get到的結(jié)果。
5. 使用CountDownLatch
使用CountDownLatch或許是日常編程中最常見的一種了,也感覺是相對優(yōu)雅的一種:
public class Demo4 extends BaseDemo{ private final CountDownLatch countDownLatch = new CountDownLatch(1); @Override public void callback(long response) { System.out.println("得到結(jié)果"); System.out.println(response); System.out.println("調(diào)用結(jié)束"); countDownLatch.countDown(); } public static void main(String[] args) { Demo4 demo4 = new Demo4(); demo4.call(); try { demo4.countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主線程內(nèi)容"); } }
正如大家平時(shí)使用的那樣,此處在主線程中利用CountDownLatch的await方法進(jìn)行阻塞,在回調(diào)中利用countDown方法來使得其他線程await的部分得以繼續(xù)運(yùn)行。
當(dāng)然,這里和demo1和demo2中都一樣,主線程中阻塞的部分,都可以設(shè)置一個(gè)超時(shí)時(shí)間,超時(shí)后可以不再阻塞。
6. 使用CyclicBarrier
CyclicBarrier的情況和CountDownLatch有些類似:
public class Demo5 extends BaseDemo{ private CyclicBarrier cyclicBarrier = new CyclicBarrier(2); @Override public void callback(long response) { System.out.println("得到結(jié)果"); System.out.println(response); System.out.println("調(diào)用結(jié)束"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } public static void main(String[] args) { Demo5 demo5 = new Demo5(); demo5.call(); try { demo5.cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("主線程內(nèi)容"); } }
大家注意一下,CyclicBarrier和CountDownLatch僅僅只是類似,兩者還是有一定區(qū)別的。比如,一個(gè)可以理解為做加法,等到加到這個(gè)數(shù)字后一起運(yùn)行;一個(gè)則是減法,減到0繼續(xù)運(yùn)行。一個(gè)是可以重復(fù)計(jì)數(shù)的;另一個(gè)不可以等等等等。
另外,使用CyclicBarrier的時(shí)候要注意兩點(diǎn)。第一點(diǎn),初始化的時(shí)候,參數(shù)數(shù)字要設(shè)為2,因?yàn)楫惒秸{(diào)用這里是一個(gè)線程,而主線程是一個(gè)線程,兩個(gè)線程都await的時(shí)候才能繼續(xù)執(zhí)行,這也是和CountDownLatch區(qū)別的部分。第二點(diǎn),也是關(guān)于初始化參數(shù)的數(shù)值的,和這里的demo無關(guān),在平時(shí)編程的時(shí)候,需要比較小心,如果這個(gè)數(shù)值設(shè)置得很大,比線程池中的線程數(shù)都大,那么就很容易引起死鎖了。
總結(jié)
綜上,就是本次需要說的幾種方法了。事實(shí)上,所有的方法都是同一個(gè)原理,也就是在調(diào)用的線程中進(jìn)行阻塞等待結(jié)果,而在回調(diào)中函數(shù)中進(jìn)行阻塞狀態(tài)的解除。
以上就是Java異步調(diào)用轉(zhuǎn)同步的方法的詳細(xì)內(nèi)容,更多關(guān)于Java異步調(diào)用轉(zhuǎn)同步的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)后臺發(fā)送及接收json數(shù)據(jù)的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)后臺發(fā)送及接收json數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了java針對json格式數(shù)據(jù)的傳輸與操作相關(guān)技巧,需要的朋友可以參考下2018-12-12SpringCloud Gateway 利用 Mysql 實(shí)現(xiàn)動態(tài)路由的方法
這篇文章主要介紹了SpringCloud Gateway 利用 Mysql 實(shí)現(xiàn)動態(tài)路由的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02java基于jdbc連接mysql數(shù)據(jù)庫功能實(shí)例詳解
這篇文章主要介紹了java基于jdbc連接mysql數(shù)據(jù)庫功能,結(jié)合實(shí)例形式詳細(xì)分析了jdbc連接mysql數(shù)據(jù)庫的原理、步驟、實(shí)現(xiàn)方法及相關(guān)操作技巧,需要的朋友可以參考下2017-10-10mybatis-puls中的resultMap數(shù)據(jù)映射
這篇文章主要介紹了mybatis-puls中的resultMap數(shù)據(jù)映射,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08springboot+mybatis如何屏蔽掉mybatis日志
這篇文章主要介紹了springboot+mybatis如何屏蔽掉mybatis日志問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05SpringBoot將Spring fox更換為Springdoc的方法詳解
由于項(xiàng)目中使用Spring fox已經(jīng)不維護(hù)更新了,代碼掃描,掃出問題,需要將Spring fox更換為Spring Doc,所以本文給大家介紹了SpringBoot將Spring fox更換為Springdoc的方法,文中有相關(guān)的代碼供大家參考,需要的朋友可以參考下2024-01-01java獲取ip地址與網(wǎng)絡(luò)接口的方法示例
這篇文章主要給大家介紹了關(guān)于利用java如何獲取ip地址與網(wǎng)絡(luò)接口的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01SpringBoot全局異常與數(shù)據(jù)校驗(yàn)的方法
這篇文章主要介紹了SpringBoot全局異常與數(shù)據(jù)校驗(yàn)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11spring boot項(xiàng)目沒有mainClass如何實(shí)現(xiàn)打包運(yùn)行
這篇文章主要介紹了spring boot項(xiàng)目沒有mainClass如何實(shí)現(xiàn)打包運(yùn)行,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01