Java異步調用轉同步的5種方式


1、異步和同步的概念

 同步調用:調用方在調用過程中,持續等待返回結果。
 異步調用:調用方在調用過程中,不直接等待返回結果,而是執行其他任務,結果返回形式通常為回調函數。

2 、異步轉為同步的概率

  需要在異步調用過程中,持續阻塞至獲得調用結果。

3、異步調用轉同步的5種方式

1、使用wait和notify方法
2、使用條件鎖
3、Future
4、使用CountDownLatch
5、使用CyclicBarrier

4、構造一個異步調用模型。

我們主要關心call方法,這個方法接收了一個demo參數,並且開啟了一個線程,在線程中執行具體的任務,並利用demo的callback方法進行回調函數的調用。大家注意到了這里的返回結果就是一個[0,10)的長整型,並且結果是幾,就讓線程sleep多久——這主要是為了更好地觀察實驗結果,模擬異步調用過程中的處理時間。至於futureCall和shutdown方法,以及線程池tp都是為了demo3利用Future來實現做准備的。

   
public class AsyncCall {    
private Random random = new Random(System.currentTimeMillis());   
 private ExecutorService tp = Executors.newSingleThreadExecutor();    //demo1,2,4,5調用方法
    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調用方法
    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();

    }

}

demo的基類:

public abstract class BaseDemo {  
      protected AsyncCall asyncCall = new AsyncCall();  
      public abstract void callback(long response);  
      public void call(){
        System.out.println("發起調用");
        asyncCall.call(this);
        System.out.println("調用返回");
    }

}

5、各種方法的具體實現

5.1、使用wait和notify方法

可以看到在發起調用后,主線程利用wait進行阻塞,等待回調中調用notify或者notifyAll方法來進行喚醒。注意,和大家認知的一樣,這里wait和notify都是需要先獲得對象的鎖的。在主線程中最后我們打印了一個內容,這也是用來驗證實驗結果的,如果沒有wait和notify,主線程內容會緊隨調用內容立刻打印;而像我們上面的代碼,主線程內容會一直等待回調函數調用結束才會進行打印。
沒有使用同步操作的情況下,打印結果:

public class Demo1 extends BaseDemo{    private final Object lock = new Object();    @Override
      public void callback(long response) {
        System.out.println("得到結果");
        System.out.println(response);
        System.out.println("調用結束");        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("主線程內容");

    }
}

沒有使用同步操作的情況下,打印結果:

發起調用
調用返回
主線程內容
得到結果
1
調用結束

而使用了同步操作后:

發起調用
調用返回
得到結果
9
調用結束
主線程內容

5.2、使用條件鎖

本上和方法5.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("得到結果");
        System.out.println(response);
        System.out.println("調用結束");
        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("主線程內容");
    }
}

5.3、Future

使用Future的方法和之前不太一樣,我們調用的異步方法也不一樣

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("發起調用");
        Future<Long> future = demo3.call();
        System.out.println("返回結果");      
  while (!future.isDone() && !future.isCancelled());      
  try {
            System.out.println(future.get());
        } catch (InterruptedException e)
         {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("主線程內容");

    }
}

5.4、CountDownLatch

使用CountDownLatch或許是日常編程中最常見的一種了,也感覺是相對優雅的一種:

public class Demo4 extends BaseDemo{ 
   private final CountDownLatch countDownLatch = new CountDownLatch(1); 
   @Override
    public void callback(long response) {

        System.out.println("得到結果");
        System.out.println(response);
        System.out.println("調用結束");

        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("主線程內容");

    }

正如大家平時使用的那樣,此處在主線程中利用CountDownLatch的await方法進行阻塞,在回調中利用countDown方法來使得其他線程await的部分得以繼續運行。
當然,這里和demo1和demo2中都一樣,主線程中阻塞的部分,都可以設置一個超時時間,超時后可以不再阻塞

5.5、CyclicBarrier

CyclicBarrier的情況和CountDownLatch有些類似:

public class Demo5 extends BaseDemo{   
 private CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 
   @Override
    public void callback(long response) {

        System.out.println("得到結果");
        System.out.println(response);
        System.out.println("調用結束");        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("主線程內容");

    }
}

大家注意一下,CyclicBarrier和CountDownLatch僅僅只是類似,兩者還是有一定區別的。比如,一個可以理解為做加法,等到加到這個數字后一起運行;一個則是減法,減到0繼續運行。一個是可以重復計數的;另一個不可以等等等等。
另外,使用CyclicBarrier的時候要注意兩點。第一點,初始化的時候,參數數字要設為2,因為異步調用這里是一個線程,而主線程是一個線程,兩個線程都await的時候才能繼續執行,這也是和CountDownLatch區別的部分。第二點,也是關於初始化參數的數值的,和這里的demo無關,在平時編程的時候,需要比較小心,如果這個數值設置得很大,比線程池中的線程數都大,那么就很容易引起死鎖了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM