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ù)。
其實,兩者的區(qū)別還是很明顯的,這里也不再細說,我們主要來說一下Java如何將異步調(diào)用轉(zhuǎn)為同步。換句話說,就是需要在異步
調(diào)用過程中,持續(xù)阻塞至獲得調(diào)用結(jié)果。
不賣關(guān)子,先列出五種方法,然后一一舉例說明:
- 使用wait和notify方法
- 使用條件鎖
- Future
- 使用CountDownLatch
- 使用CyclicBarrier
0.構(gòu)造一個異步調(diào)用
首先,寫demo需要先寫基礎(chǔ)設(shè)施,這里的話主要是需要構(gòu)造一個異步調(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方法,這個方法接收了一個demo參數(shù),并且開啟了一個線程,在線程中執(zhí)行具體的任務(wù),并利用demo的callback方法進行回調(diào)函數(shù)的調(diào)用。大家注意到了這里的返回結(jié)果就是一個[0,10)的長整型,并且結(jié)果是幾,就讓線程sleep多久——這主要是為了更好地觀察實驗結(jié)果,模擬異步調(diào)用過程中的處理時間。
至于futureCall和shutdown方法,以及線程池tp都是為了demo3利用Future來實現(xià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非常簡單,里面包含一個異步調(diào)用類的實例,另外有一個call方法用于發(fā)起異步調(diào)用,當然還有一個抽象方法callback需要每個demo去實現(xiàn)的——主要在回調(diào)中進行相應(yīng)的處理來達到異步調(diào)用轉(zhuǎn)同步的目的。
1. 使用wait和notify方法
這個方法其實是利用了鎖機制,直接貼代碼:
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進行阻塞,等待回調(diào)中調(diào)用notify或者notifyAll方法來進行喚醒。注意,和大家認知的一樣,這里wait和notify都是需要先獲得對象的鎖的。在主線程中最后我們打印了一個內(nèi)容,這也是用來驗證實驗結(jié)果的,如果沒有wait和notify,主線程內(nèi)容會緊隨調(diào)用內(nèi)容立刻打?。欢裎覀兩厦娴拇a,主線程內(nèi)容會一直等待回調(diào)函數(shù)調(diào)用結(jié)束才會進行打印。
沒有使用同步操作的情況下,打印結(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)容
2. 使用條件鎖
和方法一的原理類似:
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ū)別,只是這里使用了條件鎖,兩者的鎖機制有所不同。
3. 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提交一個Callable,然后返回一個Future,這個Future就是我們demo3中call中得到的,得到future對象之后就可以關(guān)閉線程池啦,調(diào)用asyncCall的shutdown方法。關(guān)于關(guān)閉線程池這里有一點需要注意,我們回過頭來看看asyncCall的shutdown方法:
public void shutdown(){
tp.shutdown();
}
發(fā)現(xiàn)只是簡單調(diào)用了線程池的shutdown方法,然后我們說注意的點,這里最好不要用tp的shutdownNow方法,該方法會試圖去中斷線程中中正在執(zhí)行的任務(wù);也就是說,如果使用該方法,有可能我們的future所對應(yīng)的任務(wù)將被中斷,無法得到執(zhí)行結(jié)果。
然后我們關(guān)注主線程中的內(nèi)容,主線程的阻塞由我們自己來實現(xiàn),通過future的isDone和isCancelled來判斷執(zhí)行狀態(tài),一直到執(zhí)行完成或被取消。隨后,我們打印get到的結(jié)果。
4. 使用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)容");
}
}
正如大家平時使用的那樣,此處在主線程中利用CountDownLatch的await方法進行阻塞,在回調(diào)中利用countDown方法來使得其他線程await的部分得以繼續(xù)運行。
當然,這里和demo1和demo2中都一樣,主線程中阻塞的部分,都可以設(shè)置一個超時時間,超時后可以不再阻塞。
5. 使用CyclicBarrier
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ū)別的。比如,一個可以理解為做加法,等到加到這個數(shù)字后一起運行;一個則是減法,減到0繼續(xù)運行。一個是可以重復計數(shù)的;另一個不可以等等等等。
另外,使用CyclicBarrier的時候要注意兩點。第一點,初始化的時候,參數(shù)數(shù)字要設(shè)為2,因為異步調(diào)用這里是一個線程,而主線程是一個線程,兩個線程都await的時候才能繼續(xù)執(zhí)行,這也是和CountDownLatch區(qū)別的部分。第二點,也是關(guān)于初始化參數(shù)的數(shù)值的,和這里的demo無關(guān),在平時編程的時候,需要比較小心,如果這個數(shù)值設(shè)置得很大,比線程池中的線程數(shù)都大,那么就很容易引起死鎖了。
總結(jié)
綜上,就是本次需要說的幾種方法了。事實上,所有的方法都是同一個原理,也就是在調(diào)用的線程中進行阻塞等待結(jié)果,而在回調(diào)中函數(shù)中進行阻塞狀態(tài)的解除。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java 動態(tài)數(shù)組的實現(xiàn)示例
Java動態(tài)數(shù)組是一種可以任意伸縮數(shù)組長度的對象,本文主要介紹了Java 動態(tài)數(shù)組的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
如何讓Jackson JSON生成的數(shù)據(jù)包含的中文以unicode方式編碼
這篇文章主要介紹了如何讓Jackson JSON生成的數(shù)據(jù)包含的中文以unicode方式編碼。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
Java如何向Word模板中插入Base64圖片和數(shù)據(jù)信息
這篇文章主要介紹了Java如何向Word模板中插入Base64圖片和數(shù)據(jù)信息問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
Spring中ApplicationEvent事件機制源碼詳解
這篇文章主要介紹了Spring中ApplicationEvent事件機制源碼詳解,Spring中與事件有關(guān)的接口和類主要包括ApplicationEvent、ApplicationListener,下面來看一下Spring中事件的具體應(yīng)用,需要的朋友可以參考下2023-09-09
MyBatis連接數(shù)據(jù)庫配置的基本步驟和機制
MyBatis 是一個流行的持久層框架,它通過使用XML或注解的方式將SQL語句、存儲過程和Java方法進行綁定,從而避免了手寫大量的JDBC代碼和手動設(shè)置參數(shù)與結(jié)果集,本文給大家介紹了MyBatis連接數(shù)據(jù)庫配置的基本步驟和機制,需要的朋友可以參考下2024-05-05
使用IntelliJ IDEA調(diào)式Stream流的方法步驟
本文主要介紹了使用IntelliJ IDEA調(diào)式Stream流的方法步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
Mybatis 中Mapper使用package方式配置報錯的解決方案
這篇文章主要介紹了Mybatis 中Mapper使用package方式配置報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
一鍵清除maven倉庫中下載失敗的jar包的實現(xiàn)方法
這篇文章主要介紹了一鍵清除maven倉庫中下載失敗的jar包的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07

