首頁 > 軟體

詳解Java中非同步轉同步的六種方法

2022-06-14 18:01:04

一、問題

應用場景

應用中通過框架傳送非同步命令時,不能立刻返回命令的執行結果,而是非同步返回命令的執行結果。

那麼,問題來了,針對應用中這種非同步呼叫,能不能像同步呼叫一樣立刻獲取到命令的執行結果,如何實現非同步轉同步?

二、分析

首先,解釋下同步和非同步

  • 同步,就是發出一個呼叫時,在沒有得到結果之前,該呼叫就不返回或繼續執行後續操作。
  • 非同步,當一個非同步過程呼叫發出後,呼叫者在沒有得到結果之前,就可以繼續執行後續操作。當這個呼叫完成後,一般通過狀態、通知和回撥來通知呼叫者。

對於非同步呼叫,呼叫的返回並不受呼叫者控制。

非同步轉同步主要實現思路:所有實現原理類似,是在發出呼叫的執行緒中進行阻塞等待結果,呼叫完成後通過回撥、設定共用狀態或通知進行阻塞狀態的解除,繼續執行後續操作。

三、實現方法

通常,實現中,不會無限的等待,一般會設定一個超時時間,具體超時時間根據具體場景確定。

下面以回撥的方式介紹幾種常用實現非同步轉同步的方法:

1.輪詢與休眠重試機制

採用輪詢與休眠重試機制,執行緒將反覆在休眠和測試狀態條件中之間切換,直到超時或者狀態條件滿足繼續向下執行。這種方式,超時時間控制不準確,sleep時間需要在響應性和CPU使用率之間進行權衡。

private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待時間 5分鐘
private final Object lock = new Object();

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    synchronized(lock){
        //設定狀態條件
}
 
public Result getResult() throws ErrorCodeException {
// 1.非同步呼叫
 
// 2.阻塞等待非同步響應
    long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;
    long remaining = MILLIS_OF_WAIT_TIME;//剩餘等待時間
    while(remaining > 0){
        synchronized(lock){
            if(狀態條件未滿足){
                remaining = future - System.currentTimeMillis();
                Thread.sleep(時間具體場景確定);
            }
        }  
````}
 
//4.超時或結果正確返回,對結果進行處理
     
    return result;
}

2.wait/notify

任意一個Java物件,都擁有一組監視器方法(wait、notify、notifyAll等方法),這些方法和synchronized同步關鍵字配合,可以實現等待/通知模式。但是使用wait/notify,使執行緒的阻塞/喚醒對執行緒本身來說是被動的,要準確的控制哪個執行緒是很困難的,所以是要麼隨機喚醒等待在條件佇列上一個執行緒(notify),要麼喚醒所有的(notifyAll,但是很低效)。當多個執行緒基於不同條件在同一條件佇列上等待時,如果使用notify而不是notifyAll,很容易導致訊號丟失的問題,所以必須謹慎使用wait/notify方法。

private static long MILLIS_OF_WAIT_TIME = 300000L;// 等待時間 5分鐘
private final Object lock = new Object();

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    synchronized(lock){
        lock.notifyAll();
}
 
public Result getResult() throws ErrorCodeException {
	// 1.非同步呼叫
 
	// 2.阻塞等待非同步響應
    long future = System.currentTimeMillis() + MILLIS_OF_WAIT_TIME;
    long remaining = MILLIS_OF_WAIT_TIME;//剩餘等待時間
    synchronized(lock){
        while(條件未滿足  && remaining > 0){ //被通知後要檢查條件
            lock.wait(remaining);
            remaining = future - System.currentTimeMillis();
        }  
````}
    
	//4.超時或結果正確返回,對結果進行處理
    return result;
}

3.Lock Condition

使用Lock的Condition佇列的實現方式和wait/notify方式類似,但是Lock支援多個Condition佇列,並且支援等待狀態中響應中斷。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時間 5分鐘
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    lock.lock();//這是前提
    try {
        condition.signal();
    }finally {
        lock.unlock();
    }
}

public Result getResult() throws ErrorCodeException {
	// 1.非同步呼叫
	// 2.阻塞等待非同步響應
    lock.lock();//這是前提
    try {
        condition.await();
    } catch (InterruptedException e) {
        //TODO
    }finally {
        lock.unlock();
    }
	//4.超時或結果正確返回,對結果進行處理
    return result;
}

4.CountDownLatch

使用CountDownLatch可以實現非同步轉同步,它好比計數器,在建立範例CountDownLatch物件的時候傳入數位,每使用一次 countDown() 方法計數減1,當數位減到0時, await()方法後的程式碼將可以執行,未到0之前將一直阻塞等待。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時間 5分鐘
private final CountDownLatch countDownLatch = new CountDownLatch(1);

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    countDownLatch.countDown();
}

public Result getResult() throws ErrorCodeException {
    // 1.非同步呼叫

    // 2.阻塞等待非同步響應
    try {
        countDownLatch.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        //TODO
    }
	//4.超時或結果正確返回,對結果進行處理
    return result;
}

5.CyclicBarrier

讓一組執行緒達到一個屏障(也可以叫同步點)時被阻塞,直到等待最後一個執行緒到達屏障時,屏障才開門,所有被屏障攔截的執行緒才會繼續執行。

每個執行緒通過呼叫await方法告訴CyclicBarrier我已經到達了屏障,然後當前的的執行緒被阻塞。

private static long SECONDS_OF_WAIT_TIME = 300L;// 等待時間 5分鐘
private final CountDownLatch cyclicBarrier= new CyclicBarrier(2);//設定屏障攔截的執行緒數為2

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    //我也到達屏障了,可以開門了
    cyclicBarrier.await();
}

public Result getResult() throws ErrorCodeException {
    // 1.非同步呼叫
    // 2.阻塞等待非同步響應
    try {
        //我到達屏障了,還沒開門,要等一等
        cyclicBarrier.await(SECONDS_OF_WAIT_TIME, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        //TODO
    }
	//4.超時或結果正確返回,對結果進行處理
    return result;
}

CountDownLatch和CyclicBarrier實現類似,區別是CountDownLatch的計數器只能使用一次,而CyclicBarrier的計數器可以使用reset重置,

所以CyclicBarrier能處理更為複雜的業務場景。在非同步轉同步中,計數器不會重用,所以使用CountDownLatch實現更適合。

6.LockSupport

LockSupport定義了一組公共靜態方法,提供了最基本的執行緒阻塞和喚醒的方法。

private static long NANOS_OF_WAIT_TIME = 300000000L;// 等待時間 5分鐘
private final LockSupport lockSupport = new LockSupport();

//3.結果返回後進行回撥,解除阻塞
@Override
public void callback(AsynResponse response){
    lockSupport.unpark();
}

public Result getResult() throws ErrorCodeException {
    // 1.非同步呼叫

    // 2.阻塞等待非同步響應
    try {
        lockSupport.parkNanos(NANOS_OF_WAIT_TIME);
    } catch (InterruptedException e) {
        //TODO
    }
	//4.超時或結果正確返回,對結果進行處理
    return result;

}

到此這篇關於詳解Java中非同步轉同步的六種方法的文章就介紹到這了,更多相關Java非同步轉同步內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com