对习惯了单线程的程序员来说,转移到多线程环境时,最难掌握的事情之一是如何返回线程信息。
大多数人采用的解决办法是轮询,让获取方法在结果字段设置之前返回一个标志值,然后主线程定时询问获取方法。这种方法能起到作用,但是这样也做了大量的工作!
事实上,还有一个简单有效的办法,就是让线程自己告诉主程序何时结束.它通过调用主程序中的一个方法来做到这一点,在它结束时,回头调用其创建者。这就是所谓的回调(callback)
下面的例子将说明
例1:CallBackDigest
import java.io.File;
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
public class CallBackDigest implements Runnable{
private File file;
public CallBackDigest(File file){
this.file=file;
}
public void run() {
try{
FileInputStream fos=new FileInputStream(file);
MessageDigest md=MessageDigest.getInstance("SHA");
DigestInputStream dis=new DigestInputStream(fos,md);
int b=0;
while((b=dis.read())!=-1);
dis.close();
byte[] digest=md.digest();
CallbackDigestUserInterface.receiveDigest(digest,file.getName());
}catch(Exception e){
e.printStackTrace();
}
}
}
例1中的CallbackDigestUserInterface类中提供receiveDigest静态方法。
例二:CallbackDigestUserInterface
public class CallbackDigestUserInterface {
public static void receiveDigest(byte[] digest, String name) {
StringBuffer result=new StringBuffer(name);
result.append(":");
for(int i=0;i<digest.length;i++){
result.append(digest[i]+" ");
}
System.out.println(result);
}
}
上例中是利用静态函数方法进行回调。但是,与静态方法相比,回调实例方法也不是很难实现,这种情况下,进行回调的类就必须要有其回调的对象的引用,下列说明
例3:
import java.io.File;
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
public class InstanceCallbackDigest implements Runnable{
private File file;
private InstanceCallbackDigestUserInterface icdu;
public InstanceCallbackDigest(File file,InstanceCallbackDigestUserInterface icdu){
this.file=file;
this.icdu=icdu
}
public void run() {
try{
FileInputStream fos=new FileInputStream(file);
MessageDigest md=MessageDigest.getInstance("SHA");
DigestInputStream dis=new DigestInputStream(fos,md);
int b=0;
while((b=dis.read())!=-1);
dis.close();
byte[] digest=md.digest();
icdu.receiveDigest(digest);
}catch(Exception e){
e.printStackTrace();
}
}
}
例4:InstanceCallbackDigestUserInterface
public class InstanceCallbackDigestUserInterface {
private File file;
private byte[] digest;
public CallbackDigestUserInterface(File file){
this.file=file;
}
public void calculateDigest(){
InstanceCallbackDigest icb=new InstanceCallbackDigest(file,this);
Thread t=new Thread(icb);
t.start();
}
public void receiveDigest(byte[] digest){
this.digest=digest;
System.out.println(this);
}
public String toString(){
String result=this.file.getName()+":";
if(this.digest!=null){
for(int i=0;i<digest.length;i++){
result+=digest[i];
}
}else
{
result+="digest not available";
}
return result;
}
public static void main(String args[]){
for(int i=0;i<args.length;i++){
File file=new File(args[i]);
InstanceCallbackDigestUserInterface icdui=new IntanceCallbackDigestUserInterface(f);
icdui.calculateDigest();
}
}
}
使用实例方法进行回调比使用静态方法回调复杂一些,但这种方法根据灵活性。值得注意的是,这个类新增的启动线程的方法,从逻辑上考虑这可能是构造函数。但是,在构造函数里启动线程是很危险的,特别是但线程回调初始化对象的时候。
这里存在一个竞争条件可能会让新线程在构造函数结束和对象完全初始化之前进行回调。
在有多个对象关心线程计算结果的时候,那么可以再线程类中保存回调对象列表。如果有多个类的实例关心结果,可以定义一个新的interface将声明回调方法,这正是在Swing,AWT中的处理的方法,组件通过回调在某个接口(如ActionListener)中声明的方法来通知事件的发生。监听者对象使用Component类中的方法,如addActionListener(),以表示对某个组件所发生事件的关注。在组件中,注册的监听者存储在java.awt.AWTEventMultivaster组成的链表中。这样,就可以很容易的利用这种模式了!
例5:DigestListener
public inteerface DigestListener{
public void digestCalculates(byte[] digest);
}
例6展示计算摘要的Runnable类。它添加用来注册和撤销注册监听者的方法。run()方法不在直接回调创建它的对象。相反,他与sendDigest()方法通信,这个方法把摘要发给所有注册的监听者。
public class ListCallbackDigest implements Runnable{
private File file;
List listenerList=new Vector();
public ListCallbackDigest(File file){
this.file=file;
}
public synchronized void addDigestListener(DigestListener dl){
listenerList.add(dl);
}
public synchronized void removeDigestListener(DigestListener dl){
listenerList.remove(dl);
}
private synchronized void sendDigest(byte[] digest){
ListIterator iterator=listenerList.listIterator();
while(iterator.hasnext()){
DigestListener dl=(DigestListener)iterator.next();
dl.digestCaculated(digest);
}
}
public void run(){
try{
FileInputStream fos=new FileInputStream(file);
MessageDigest md=MessageDigest.getInstance("SHA");
DigestInputStream dis=new DigestInputStream(fos,md);
int b=0;
while((b=dis.read())!=-1);
dis.close();
byte[] digest=md.digest();
this.sendDigest(digest,file.getName());
}catch(Exception e){
e.printStackTrace();
}
}
}
最后就是实现DigestListenerj接口了
例七:ListCallbackDigestUserInterface
publiv class ListCallbackDigestUserInterface implements DigestListener{
private File file;
private byte[] digest;
public ListCallbackDigestUserInterface(File file){
this.file=file;
}
public void calculateDigest(){
ListCallbackDigestUserInterface cb=new ListCallbackDigestUserInterface(file);
cb.addDigestListener(this);
Thread t=new Thread(cb);
t.start();
}
public void digestCalculated(byte[] digest){
this.digest=digest;
System.out.println(this);
}
public String toString(){
String result=this.file.getName()+":";
if(this.digest!=null){
for(int i=0;i<digest.length;i++){
result+=digest[i];
}
}else
{
result+="digest not available";
}
return result;
}
public static void main(String args[]){
for(int i=0;i<args.length;i++){
File file=new File(args[i]);
ListCallbackDigestUserInterface icdui=new ListCallbackDigestUserInterface(f);
icdui.calculateDigest();
}
}
}
OK啦!