Preface
原来一直不明白回调函数的作用,觉得就算是在单线程的环境下也只是用一种方式去改变程序的运行状态——从一个领域跳到另一个领域,做一个消息告知的功能,我觉得没什么很大作用。然后事情放到多线程环境下性质就截然不同了。我发现回调在多线程领域里面通知的功能还是很重要的,从控制角度来说是一种反馈机制。接下来我们就稍微来探讨一下这种机制的原理。
About Callback in Java
由于Java里并没有函数指针的概念,但是这并不影响使用回调这种思想。实际上,封装可以帮我们解决很多的问题。这也是模式里面经常强调的。将复杂的地方,存在变化的地方封装掉。只要稍微使用一点点聚合(Aggregation)就可以了。其实简单来说就是一个对象持有另一个对象的引用。更有效的做法是使用接口去规定一个回调的协议。我看网上很多写的有点复杂,所以自己按照自己的理解写了个比较存粹思路的代码。
/*首先来看一下这个工具类,我主要是想为我接下来将的回调函数是在哪个线程运行的做准备*/
package com;
public class Utils {
public static long getThreadId() {
Thread t = Thread.currentThread();
return t.getId();
}
public static String getThreadSignature() {
Thread t = Thread.currentThread();
long l = t.getId();
String name = t.getName();
return "Thread name: " + name + " id: " + l;
}
public static void printThreadSignature() {
System.out.println(getThreadSignature());
}
}
接下来来看看ICallbackListener接口
package com;
public interface ICallbackListener {
public void print(String msg);
}
接下来是回调线程ThreadCounter,这边仅仅是实现了下Runable接口
package com;
public class ThreadCounter implements Runnable {
private int counter;
private ICallbackListener listener;
public ThreadCounter() {
// TODO Auto-generated constructor stub
counter = 0;
listener = null;
}
public void addListener(ICallbackListener listener) {
this.listener = listener;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("I'm going to count number.");
for (; counter < 100; counter++) {
System.out.println("counter = " + counter);
}
System.out.println("Well, I'm done");
if (listener != null) {
listener.print(String.valueOf(counter));
}
}
}
然后是我们的Listener
package com;
public class Listener implements ICallbackListener {
@Override
public void print(String msg) {
// TODO Auto-generated method stub
Utils.printThreadSignature();
System.out.println("So, You have count to " + msg);
}
}
最后是我们的Main函数
package com;
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadCounter tc = new ThreadCounter();
Listener listener = new Listener();
tc.addListener(listener);
Thread t = new Thread(tc);
t.setName("Thread Counter");
t.start();
/*只是让主线程找点事干,没别的*/
for (int i = 0; i < 20; i++) {
System.out.println("I'm counting too , counting " + i);
}
}
}
}
我想我的代码应该写的很明白了,好的代码是不需要注释的,哈哈!如果你还不明白的话可以看下这张UML结构图
package com;
public class Utils {
public static long getThreadId() {
Thread t = Thread.currentThread();
return t.getId();
}
public static String getThreadSignature() {
Thread t = Thread.currentThread();
long l = t.getId();
String name = t.getName();
return "Thread name: " + name + " id: " + l;
}
public static void printThreadSignature() {
System.out.println(getThreadSignature());
}
}
接下来来看看ICallbackListener接口
package com;
public interface ICallbackListener {
public void print(String msg);
}
接下来是回调线程ThreadCounter,这边仅仅是实现了下Runable接口
package com;
public class ThreadCounter implements Runnable {
private int counter;
private ICallbackListener listener;
public ThreadCounter() {
// TODO Auto-generated constructor stub
counter = 0;
listener = null;
}
public void addListener(ICallbackListener listener) {
this.listener = listener;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("I'm going to count number.");
for (; counter < 100; counter++) {
System.out.println("counter = " + counter);
}
System.out.println("Well, I'm done");
if (listener != null) {
listener.print(String.valueOf(counter));
}
}
}
然后是我们的Listener
package com;
public class Listener implements ICallbackListener {
@Override
public void print(String msg) {
// TODO Auto-generated method stub
Utils.printThreadSignature();
System.out.println("So, You have count to " + msg);
}
}
最后是我们的Main函数
package com;
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadCounter tc = new ThreadCounter();
Listener listener = new Listener();
tc.addListener(listener);
Thread t = new Thread(tc);
t.setName("Thread Counter");
t.start();
/*只是让主线程找点事干,没别的*/
for (int i = 0; i < 20; i++) {
System.out.println("I'm counting too , counting " + i);
}
}
}
}
我想我的代码应该写的很明白了,好的代码是不需要注释的,哈哈!如果你还不明白的话可以看下这张UML结构图
其实我们的工作线程做的事情很简单,只是去数数而已。数到100就告诉Listener我已经数到100了。这里UML看不懂的别问我了。这里很容易看到的就是ThreadCounter持有Listener的引用,是通过接口做了一个规定。这里的规定就是一个简单print(String msg)目的是反馈给Listener状态,这样就形成了一个反馈机制。
不知道大家有没有注意到我故意在Listener的print里面加了个报告这个函数是哪个线程调。如果你的思路很清楚的话应该很容易猜到,实际上是ThreadCounter线程在使用这个函数,而不是Listener所在的线程(即Main线程)。如果你发现这个情况的话我会很自然的想到一个问题:可能会发生使用共享资源的情况。假设回调的print函数里面有用到Listener里面的属性什么的话,是没有任何问题的。然后你必须记住,回调是发生在子线程里的。如果你的Listener在Main线程里工作的话,就极有可能两个线程同时访问同一个资源(属性)。这个时候最好是做个线程锁还是什么的机制互斥一下吧,具体做法这里就不讨论了。是不是很简单呢?
不知道大家有没有注意到我故意在Listener的print里面加了个报告这个函数是哪个线程调。如果你的思路很清楚的话应该很容易猜到,实际上是ThreadCounter线程在使用这个函数,而不是Listener所在的线程(即Main线程)。如果你发现这个情况的话我会很自然的想到一个问题:可能会发生使用共享资源的情况。假设回调的print函数里面有用到Listener里面的属性什么的话,是没有任何问题的。然后你必须记住,回调是发生在子线程里的。如果你的Listener在Main线程里工作的话,就极有可能两个线程同时访问同一个资源(属性)。这个时候最好是做个线程锁还是什么的机制互斥一下吧,具体做法这里就不讨论了。是不是很简单呢?
Summary
其实回调并不难理解,平常编程的时候你可能一直在用这一种模式,只不过你没注意罢了——通过持有其他对象的引用(形成聚合),用一个接口定个协议,在里面尽情的call吧!
以下是Java工程文件欢迎下载:
以下是Java工程文件欢迎下载:
callbackex.zip |