【Java基本功】Java里的回調機制,你了解過嗎?

  • 2019 年 10 月 7 日
  • 筆記

本文主要介紹了Java中的回調機制,以及Java多執行緒中類似回調的機制。

模組間的調用

本部分摘自https://www.cnblogs.com/xrq730/p/6424471.html

在一個應用系統中,無論使用何種語言開發,必然存在模組之間的調用,調用的方式分為幾種:

(1)同步調用

同步調用是最基本並且最簡單的一種調用方式,類A的方法a()調用類B的方法b(),一直等待b()方法執行完畢,a()方法繼續往下走。這種調用方式適用於方法b()執行時間不長的情況,因為b()方法執行時間一長或者直接阻塞的話,a()方法的餘下程式碼是無法執行下去的,這樣會造成整個流程的阻塞。

(2)非同步調用

非同步調用是為了解決同步調用可能出現阻塞,導致整個流程卡住而產生的一種調用方式。類A的方法方法a()通過新起執行緒的方式調用類B的方法b(),程式碼接著直接往下執行,這樣無論方法b()執行時間多久,都不會阻塞住方法a()的執行。 但是這種方式,由於方法a()不等待方法b()的執行完成,在方法a()需要方法b()執行結果的情況下(視具體業務而定,有些業務比如啟非同步執行緒發個微信通知、刷新一個快取這種就沒必要),必須通過一定的方式對方法b()的執行結果進行監聽。 在Java中,可以使用Future+Callable的方式做到這一點,具體做法可以參見我的這篇文章Java多執行緒21:多執行緒下其他組件之CyclicBarrier、Callable、Future和FutureTask。

(3)回調

最後是回調,回調的思想是:

類A的a()方法調用類B的b()方法 類B的b()方法執行完畢主動調用類A的callback()方法 這樣一種調用方式組成了上圖,也就是一種雙向的調用方式。

回調實例:Tom做題

數學老師讓Tom做一道題,並且Tom做題期間數學老師不用盯著Tom,而是在玩手機,等Tom把題目做完後再把答案告訴老師。

1 數學老師需要Tom的一個引用,然後才能將題目發給Tom。 2 數學老師需要提供一個方法以便Tom做完題目以後能夠將答案告訴他。 3 Tom需要數學老師的一個引用,以便Tom把答案給這位老師,而不是隔壁的體育老師。

回調介面,可以理解為老師介面

//回調指的是A調用B來做一件事,B做完以後將結果告訴給A,這期間A可以做別的事情。      //這個介面中有一個方法,意為B做完題目後告訴A時使用的方法。      //所以我們必須提供這個介面以便讓B來回調。      //回調介面,      public interface CallBack {          void tellAnswer(int res);      }

數學老師類

//老師類實例化回調介面,即學生寫完題目之後通過老師的提供的方法進行回調。      //那麼學生如何調用到老師的方法呢,只要在學生類的方法中傳入老師的引用即可。      //而老師需要指定學生答題,所以也要傳入學生的實例。  public class Teacher implements CallBack{      private Student student;        Teacher(Student student) {          this.student = student;      }        void askProblem (Student student, Teacher teacher) {          //main方法是主執行緒運行,為了實現非同步回調,這裡開啟一個執行緒來操作          new Thread(new Runnable() {              @Override              public void run() {                  student.resolveProblem(teacher);              }          }).start();          //老師讓學生做題以後,等待學生回答的這段時間,可以做別的事,比如玩手機.          //而不需要同步等待,這就是回調的好處。          //當然你可以說開啟一個執行緒讓學生做題就行了,但是這樣無法讓學生通知老師。          //需要另外的機制去實現通知過程。          // 當然,多執行緒中的future和callable也可以實現數據獲取的功能。          for (int i = 1;i < 4;i ++) {              System.out.println("等學生回答問題的時候老師玩了 " + i + "秒的手機");          }      }        @Override      public void tellAnswer(int res) {          System.out.println("the answer is " + res);      }  }

學生介面

//學生的介面,解決問題的方法中要傳入老師的引用,否則無法完成對具體實例的回調。      //寫為介面的好處就是,很多個學生都可以實現這個介面,並且老師在提問題時可以通過      //傳入List<Student>來聚合學生,十分方便。  public interface Student {      void resolveProblem (Teacher teacher);  }

學生Tom

public class Tom implements Student{        @Override      public void resolveProblem(Teacher teacher) {          try {              //學生思考了3秒後得到了答案,通過老師提供的回調方法告訴老師。              Thread.sleep(3000);              System.out.println("work out");              teacher.tellAnswer(111);          } catch (InterruptedException e) {              e.printStackTrace();          }      }

測試類

public class Test {      public static void main(String[] args) {          //測試          Student tom = new Tom();          Teacher lee = new Teacher(tom);          lee.askProblem(tom, lee);          //結果  //        等學生回答問題的時候老師玩了 1秒的手機  //        等學生回答問題的時候老師玩了 2秒的手機  //        等學生回答問題的時候老師玩了 3秒的手機  //        work out  //        the answer is 111      }  }

多執行緒中的「回調」

Java多執行緒中可以通過callable和future或futuretask結合來獲取執行緒執行後的返回值。實現方法是通過get方法來調用callable的call方法獲取返回值。

其實這種方法本質上不是回調,回調要求的是任務完成以後被調用者主動回調調用者的介面。而這裡是調用者主動使用get方法阻塞獲取返回值。

public class 多執行緒中的回調 {      //這裡簡單地使用future和callable實現了執行緒執行完後      public static void main(String[] args) throws ExecutionException, InterruptedException {          ExecutorService executor = Executors.newCachedThreadPool();          Future<String> future = executor.submit(new Callable<String>() {              @Override              public String call() throws Exception {                  System.out.println("call");                  TimeUnit.SECONDS.sleep(1);                  return "str";              }          });          //手動阻塞調用get通過call方法獲得返回值。          System.out.println(future.get());          //需要手動關閉,不然執行緒池的執行緒會繼續執行。          executor.shutdown();        //使用futuretask同時作為執行緒執行單元和數據請求單元。      FutureTask<Integer> futureTask = new FutureTask(new Callable<Integer>() {          @Override          public Integer call() throws Exception {              System.out.println("dasds");              return new Random().nextInt();          }      });      new Thread(futureTask).start();      //阻塞獲取返回值      System.out.println(futureTask.get());  }  @Test  public void test () {      Callable callable = new Callable() {          @Override          public Object call() throws Exception {              return null;          }      };      FutureTask futureTask = new FutureTask(callable);    }  }