溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

利用Java怎么實(shí)現(xiàn)異步調(diào)用轉(zhuǎn)同步

發(fā)布時間:2020-12-30 14:19:05 來源:億速云 閱讀:119 作者:Leah 欄目:開發(fā)技術(shù)

利用Java怎么實(shí)現(xiàn)異步調(diào)用轉(zhuǎn)同步?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

1.構(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方法進(jìn)行回調(diào)函數(shù)的調(diào)用。大家注意到了這里的返回結(jié)果就是一個[0,10)的長整型,并且結(jié)果是幾,就讓線程sleep多久——這主要是為了更好地觀察實(shí)驗(yàn)結(jié)果,模擬異步調(diào)用過程中的處理時間。
至于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非常簡單,里面包含一個異步調(diào)用類的實(shí)例,另外有一個call方法用于發(fā)起異步調(diào)用,當(dāng)然還有一個抽象方法callback需要每個demo去實(shí)現(xiàn)的——主要在回調(diào)中進(jìn)行相應(yīng)的處理來達(dá)到異步調(diào)用轉(zhuǎn)同步的目的。

2. 使用wait和notify方法

這個方法其實(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都是需要先獲得對象的鎖的。在主線程中最后我們打印了一個內(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提交一個Callable,然后返回一個Future,這個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)容");

  }
}

正如大家平時使用的那樣,此處在主線程中利用CountDownLatch的await方法進(jìn)行阻塞,在回調(diào)中利用countDown方法來使得其他線程await的部分得以繼續(xù)運(yùn)行。
當(dāng)然,這里和demo1和demo2中都一樣,主線程中阻塞的部分,都可以設(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ū)別的。比如,一個可以理解為做加法,等到加到這個數(shù)字后一起運(yùn)行;一個則是減法,減到0繼續(xù)運(yùn)行。一個是可以重復(fù)計數(shù)的;另一個不可以等等等等。
另外,使用CyclicBarrier的時候要注意兩點(diǎn)。第一點(diǎn),初始化的時候,參數(shù)數(shù)字要設(shè)為2,因?yàn)楫惒秸{(diào)用這里是一個線程,而主線程是一個線程,兩個線程都await的時候才能繼續(xù)執(zhí)行,這也是和CountDownLatch區(qū)別的部分。第二點(diǎn),也是關(guān)于初始化參數(shù)的數(shù)值的,和這里的demo無關(guān),在平時編程的時候,需要比較小心,如果這個數(shù)值設(shè)置得很大,比線程池中的線程數(shù)都大,那么就很容易引起死鎖了。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI