1. 程式人生 > >Java基礎9——深入理解java回撥機制

Java基礎9——深入理解java回撥機制

Java回撥機制

回撥的引入故事 轉載自Bro_超

// 同步回撥
1 public interface doJob
2 {
3     public void fillBlank(int a, int b, int result);
4 }
1 public class SuperCalculator
2 {
3     public void add(int a, int b, doJob  customer)
4     {
5         int result = a + b;
6         customer.fillBlank(a, b, result)
; 7 } 8 }
 1 public class Student
 2 {
 3     private String name = null;
 4 
 5     public Student(String name)
 6     {
 7         // TODO Auto-generated constructor stub
 8         this.name = name;
 9     }
10     
11     public void setName(String name)
12     {
13         this.name = name;
14 } 15 16 public class doHomeWork implements doJob 17 { 18 19 @Override 20 public void fillBlank(int a, int b, int result) 21 { 22 // TODO Auto-generated method stub 23 System.out.println(name + "求助小紅計算:" + a + " + " + b + " = " + result)
; 24 } 25 26 } 27 28 public void callHelp (int a, int b) 29 { 30 new SuperCalculator().add(a, b, new doHomeWork()); 31 } 32 }
 1 public class Seller
 2 {
 3     private String name = null;
 4 
 5     public Seller(String name)
 6     {
 7         // TODO Auto-generated constructor stub
 8         this.name = name;
 9     }
10     
11     public void setName(String name)
12     {
13         this.name = name;
14     }
15     
16     public class doHomeWork implements doJob
17     {
18 
19         @Override
20         public void fillBlank(int a, int b, int result)
21         {
22             // TODO Auto-generated method stub
23             System.out.println(name + "求助小紅算賬:" + a + " + " + b + " = " + result + "元");
24         }
25         
26     }
27     
28     public void callHelp (int a, int b)
29     {
30         new SuperCalculator().add(a, b, new doHomeWork());
31     }
32 }
1 public class Test
 2 {
 3     public static void main(String[] args)
 4     {
 5         int a = 56;
 6         int b = 31;
 7         int c = 26497;
 8         int d = 11256;
 9         Student s1 = new Student("小明");
10         Seller s2 = new Seller("老婆婆");
11         
12         s1.callHelp(a, b);
13         s2.callHelp(c, d);
14     }
15 }

模組間的呼叫

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

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把題目做完後再把答案告訴老師。

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

//回撥指的是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);

}
}