帶你一步步實現線程池異步回調

轉載請註明出處

作者:曉渡
文章地址:https://greatestrabit.github.io/2016/03/29/callback/

1.字面意義上的回調

字面意思上理解回調,就是A調用B,B回過頭來再調用A,即是回調.既然是這樣,當然就要求A中有B,B中有A.如下:

class A {
	/**
	 * 提出問題
	 * @author [email protected]
	 * @param b
	 * @param question
	 */
	public void ask(final B b, final String question) {
		b.answer(this, question);
	}

	/**
	 * 處理結果
	 * @author [email protected]
	 * @param answer
	 */
	public void proce***esult(final String answer) {
		System.out.println(answer);
	}
}

class B {
	/**
	 * 計算結果
	 * @author [email protected]
	 * @param a
	 * @param question
	 */
	public void answer(final A a, final String question) {
		if (question.equals("What is the answer to life, the universe and everything?")) {
			a.proce***esult("42");
		}
	}
}

/**
 * 相互調用
 * @author [email protected]
 *
 */
public class SyncObjectCallback {
	public static void main(final String[] args) {
		B b = new B();
		A a = new A();

		a.ask(b, "What is the answer to life, the universe and everything?");
	}
}

2.面向對象的回調

上面的寫法中,B的對象只在方法中被傳遞了.實際上,這個B對象後來又調用了A中的方法,它的作用應該不止侷限在一個方法中,而應該是A的一個部分.也就是,上面的寫法不夠”面向對象”,讓我們來改造一下:

class A {
	private final B b;

	public A(final B b) {
		this.b = b;
	}

	public void ask(final String question) {
		this.b.answer(this, question);
	}

	public void proce***esult(final String answer) {
		System.out.println(answer);
	}
}

class B {
	public void answer(final A a, final String question) {
		if (question.equals("What is the answer to life, the universe and everything?")) {
			a.proce***esult("42");
		}
	}
}

/**
 * 面向對象的相互調用
 * @author [email protected]
 *
 */
public class SyncOOCallback {
	public static void main(final String[] args) {
		B b = new B();
		A a = new A(b);
		a.ask("What is the answer to life, the universe and everything?");
	}
}

3.面向接口的回調

上面的兩個例子,估計沒人會承認也是回調吧.因爲並沒什麼卵用.不過這個流程對於理解回調是很重要的.其實回調真正有用的地方,在於它的”預測”能力.
我們擴展想象一下.假設上面例子中的B,爲A提供了很多服務之後突然覺醒,想爲更多的對象提供服務,這樣一來,B就變成了Server.而且還要制定規則.規則是什麼呢,就是要Server提供服務可以,對方一定要有一個recvAnswer接口供Server調用才行,這樣Server才能把結果傳回給Client.具體如何制定規則呢?通過Interface.如下:

/**
 * 發出請求着需要實現的接口,要實現處理結果的方法
 * @author [email protected]
 *
 */
public interface IClient {
	void recvAnswer(String answer);
}
  
/**
 * 響應請求者,即提供服務者
 * @author [email protected]
 *
 */
public class Server {
	public void answer(final IClient client, final String question) {
		if (question.equals("What is the answer to life, the universe and everything?")) {
			calclating();
			client.recvAnswer("42");
		}
	}

	private void calclating() {
		try {
			Thread.sleep(new Random().nextInt(5000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
  
/**
 * 發出請求者,同時要處理請求結果
 * @author [email protected]
 *
 */
public class ClientSync implements IClient {
	private final Server server;

	public ClientSync(final Server server) {
		this.server = server;
	}

	public void ask(final String question) {
		this.server.answer(this, question);
	}

	@Override
	public void recvAnswer(final String answer) {
		System.out.println(answer);
	}
}
  
/**
 * 面向接口的同步回調
 * @author [email protected]
 *
 */
public class SyncInterfaceCallback {
	/**
	 * 使用內部類來實現的方式
	 * @author [email protected]
	 */
	private static void innerMain() {
		Server server = new Server();
		server.answer(new IClient() {
			@Override
			public void recvAnswer(final String answer) {
				System.out.println(answer);
			}
		}, "What is the answer to life, the universe and everything?");
	}

	public static void main(final String[] args) {
		Server server = new Server();
		ClientSync client = new ClientSync(server);
		client.ask("What is the answer to life, the universe and everything?");

		innerMain();
	}
}

注意,接口IClient實際上應該是屬於Server端的,它是由Server制定的,需要Client來實現的接口,雖然看上去它跟Client很近.
爲什麼說有”預測”能力呢?想象另一個場景.Server現在是一個底層服務,這個底層服務知道遲早有一天會有高層服務來討要數據,但是數據如何向上傳遞呢?底層可以承諾,只有你實現IClient接口,我就會調用其中的recvAnswer方法,把數據傳上來.現在底層也可以調用高層的方法,算是有”預測”能力吧?



4.異步回調

上面的調用都是同步的.假設Server計算結果需要較長的時間,你一定希望它能在一個單獨的線程中被執行,這是就可以把ask方法的調用用線程包裝一下:

public class ClientAsync implements IClient {
	private final Server server;

	public ClientAsync(final Server server) {
		this.server = server;
	}

	/**
	 * 在線程中發出請求
	 * @author [email protected]
	 * @param question
	 */
	public void ask(final String question) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				server.answer(ClientAsync.this, question);
			}
		}).start();
	}

	@Override
	public void recvAnswer(final String answer) {
		System.out.println(answer);
	}
}
  
/**
 * 基於接口的異步回調,每次建立新的線程
 * @author [email protected]
 *
 */
public class AsyncInterfaceCallback {
	/**
	 * 使用內部類的實現方式,此處可見回調地獄
	 * @author [email protected]
	 */
	private static void innerMain() {
		Server server = new Server();

		new Thread(new Runnable() {
			@Override
			public void run() {
				server.answer(new IClient() {
					@Override
					public void recvAnswer(final String answer) {
						System.out.println(answer);
					}
				}, "What is the answer to life, the universe and everything?");
			}
		}).start();
		System.out.println("asked ! waiting for the answer...");
	}

	public static void main(final String[] args) {
		Server server = new Server();
		ClientAsync client = new ClientAsync(server);
		client.ask("What is the answer to life, the universe and everything?");
		System.out.println("asked ! waiting for the answer...");

		innerMain();
	}
}


5.線程池異步回調

每次建立新的線程耗費資源巨大,爲了重用線程,使用線程池管理異步調用,這時候就要求Client不僅要實現IClient接口,還要同時是一個任務,才能被線程池執行,如下:

/**
 * 專門用來執行請求的任務,供線程池調用
 * @author [email protected]
 *
 */
public class ClientRunnable implements IClient, Runnable {
	private final Server server;
	private final String question;
	private final int id;

	public ClientRunnable(final Server server, final String question, final int id) {
		this.server = server;
		this.question = question;
		this.id = id;
	}

	@Override
	public void recvAnswer(final String answer) {
		System.out.println("clinet " + this.id + " got answer: " + answer);
	}

	@Override
	public void run() {
		server.answer(ClientRunnable.this, this.question);
	}
}
  
/**
 * 基於線程池的異步回調
 * @author [email protected]
 *
 */
public class ThreadpoolCallback {
	public static void main(final String[] args) {
		ExecutorService es = Executors.newCachedThreadPool();

		Server server = new Server();

		for (int i = 0; i < 100; i++) {
			ClientRunnable cr = new ClientRunnable(server, "What is the answer to life, the universe and everything?",
					i);
			es.execute(cr);
			System.out.println("client " + i + " asked !");
		}

		es.shutdown();
	}
}


至此,我們就實現了線程池異步回調.

完整源碼請移步:github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章