您的位置:首页 > 编程语言 > Java开发

Java线程间通信-回调的实现方式

2011-02-20 20:47 489 查看
Quotes: http://lavasoft.blog.51cto.com/62575/98796
Java线程间通信-回调的实现方式



Java线程间通信是非常复杂的问题的。线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互。

比如举一个简单例子,有一个多线程的类,用来计算文件的MD5码,当多个这样的线程执行的时候,将每个文件的计算的结果反馈给主线程,并从控制台输出。

线程之间的通讯主要靠回调来实现,回调的概念说得抽象了很难理解,等于没说。我就做个比喻:比如,地铁的列车上有很多乘客,乘客们你一句他一句
的问“到XX站了没?”,列车长肯定会很烦!于是乎,车长告诉大家,你们都各干各的事情,不用问了,到站了我会通知你们的。 这就是回调!

在上面这个例子中,列车长是一个多线程类,他的工作就是开车,到站后他要将到站的信息反馈给乘客线程。

以上面文件摘要码的计算为蓝本,下面探索Java线程间的通信问题:

方式一:静态方法回调

import
java.io.File;

import
java.io.FileInputStream;

import
java.io.FileNotFoundException;

import
java.io.IOException;

import
java.security.MessageDigest;

import
java.security.NoSuchAlgorithmException;

import
java.security.DigestInputStream;

/**

* 求文件的信息摘要码(MD5)

*

* @author leizhimin 2008-9-11 22:53:39

*/

public
class
CallbackDigest implements
Runnable {

private
File inputFile; //目标文件

public
CallbackDigest(File input) {

this
.inputFile = input;

}

public
void
run() {

try
{

FileInputStream in = new
FileInputStream(inputFile);

MessageDigest sha = MessageDigest.getInstance("MD5"
);

DigestInputStream din = new
DigestInputStream(in, sha);

int
b;

while
((b = din.read()) != -1) ;

din.close();

byte
[] digest = sha.digest(); //摘要码

//完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程

CallbackDigestUserInterface.receiveDigest(digest, inputFile.getName());

} catch
(FileNotFoundException e) {

e.printStackTrace();

} catch
(NoSuchAlgorithmException e) {

e.printStackTrace();

} catch
(IOException e) {

e.printStackTrace();

}

}

}

import
java.io.File;

/**

* 静态非同步回调

*

* @author leizhimin 2008-9-11 23:00:12

*/

public
class
CallbackDigestUserInterface {

/**

* 接收摘要码,输出到控制台

*

* @param digest 摘要码

* @param inputFileName 输入的文件名

*/

public
static
void
receiveDigest(byte
[] digest, String inputFileName) {

StringBuffer result = new
StringBuffer(inputFileName);

result.append(": "
);

for
(int
j = 0; j < digest.length; j++) {

result.append(digest[j] + " "
);

}

System.out.println(result);

}

public
static
void
main(String[] args) {

String arr[] = {"C://xcopy.txt"
, "C://x.txt"
, "C://xb.txt"
, "C://bf2.txt"
};

args = arr;

for
(int
i = 0; i < args.length; i++) {

File f = new
File(args[i]);

CallbackDigest cb = new
CallbackDigest(f);

Thread t = new
Thread(cb);

t.start();

}

}

}

bf2.txt: 31 -37 46 -53 -26 -45 36 -105 -89 124 119 111 28 72 74 112

xb.txt: 112 -81 113 94 -65 -101 46 -24 -83 -55 -115 18 -1 91 -97 98

x.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47

xcopy.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47

Process finished with exit code 0

这里的receiveDigest(byte[] digest, String inputFileName)没有同步控制,当多线程乱序执行的时候,可能会影响输出的次序等问题。

因此可以将此方法改为同步方法:有两种方式,一种在方法上加synchronized关键字修饰。一种是用synchronized(System.out)对象锁来同步输入控制台的代码部分。

方式二:实例方法回调
上面的方法过于死板,所有的多线程通讯都必须那么掉。不能搞特殊化,为了更加的灵活性,选择实例方法回调是一个不错的选择。
原理是,将回调类定义为一个实现某种接口的类(接口可以省掉),然后在每个多线程类上都注入一个回调对象。当线程执行完毕后,通过回调对象执行自己的回调方法,从而达到线程通信的目的。实现代码如下:

import
java.io.File;

import
java.io.FileInputStream;

import
java.io.FileNotFoundException;

import
java.io.IOException;

import
java.security.MessageDigest;

import
java.security.NoSuchAlgorithmException;

import
java.security.DigestInputStream;

/**

* 求文件的信息摘要码(MD5)

*

* @author leizhimin 2008-9-11 22:53:39

*/

public
class
InstanceCallbackDigest implements
Runnable {

private
File inputFile; //目标文件

//每个线程绑定一个回调对象

private
InstanceCallbackDigestUserInterface instanceCallback;

/**

* 构件时一次注入回调对象

*

* @param instanceCallback

* @param inputFile

*/

public
InstanceCallbackDigest(InstanceCallbackDigestUserInterface instanceCallback, File inputFile) {

this
.instanceCallback = instanceCallback;

this
.inputFile = inputFile;

}

public
void
run() {

try
{

FileInputStream in = new
FileInputStream(inputFile);

MessageDigest sha = MessageDigest.getInstance("MD5"
);

DigestInputStream din = new
DigestInputStream(in, sha);

int
b;

while
((b = din.read()) != -1) ;

din.close();

byte
[] digest = sha.digest(); //摘要码

//完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程

instanceCallback.receiveDigest(digest);

} catch
(FileNotFoundException e) {

e.printStackTrace();

} catch
(NoSuchAlgorithmException e) {

e.printStackTrace();

} catch
(IOException e) {

e.printStackTrace();

}

}

}

import
java.io.File;

/**

* 静态非同步回调

*

* @author leizhimin 2008-9-11 23:00:12

*/

public
class
InstanceCallbackDigestUserInterface {

private
File inputFile; //回调与每个文件绑定

private
byte
digest[]; //文件的消息摘要码

public
InstanceCallbackDigestUserInterface(File inputFile) {

this
.inputFile = inputFile;

}

/**

* 计算某个文件的消息摘要码

*/

public
void
calculateDigest() {

InstanceCallbackDigest callback = new
InstanceCallbackDigest(this
, inputFile);

Thread t = new
Thread(callback);

t.start();

}

/**

* 接收消息摘要码

*

* @param digest

*/

public
void
receiveDigest(byte
[] digest) {

this
.digest = digest;

//将消息摘要码输出到控制台实际上执行的是this.toString()方法

System.out.println(this
);

}

/**

* 显示结果

*

* @return 结果

*/

public
String toString() {

String result = inputFile.getName() + ": "
;

if
(digest != null
) {

for
(byte
b : digest) {

result += b + " "
;

}

} else
{

result += "digest 不可用!"
;

}

return
result;

}

public
static
void
main(String[] args) {

String arr[] = {"C://xcopy.txt"
, "C://x.txt"
, "C://xb.txt"
, "C://bf2.txt"
};

args = arr;

for
(int
i = 0; i < args.length; i++) {

File f = new
File(args[i]);

InstanceCallbackDigestUserInterface cb = new
InstanceCallbackDigestUserInterface(f);

cb.calculateDigest();

}

}

}

xcopy.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47

x.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47

xb.txt: 112 -81 113 94 -65 -101 46 -24 -83 -55 -115 18 -1 91 -97 98

bf2.txt: 31 -37 46 -53 -26 -45 36 -105 -89 124 119 111 28 72 74 112

Process finished with exit code 0

实例方法回调更加的灵活,一个文件对应一个回调对象,这样便于跟踪关于计算过程中信息而不需要额外的数据结构。其次,如果有必要,还可以重新计算指定的摘要(需要继承默认实现,然后覆盖方法)。

注意:这里的public void
calculateDigest()方法,这个方法可能在逻辑上认为它属于一个构造器。然而,在构造器中启动线程是相当危险的,特别是对开始对象回调的线
程。这里存在一个竞争条件:构造器中假如有很多的事情要做,而启动新的线程先做了,计算完成了后要回调,可是这个时候这个对象还没有初始化完成,这样就产
生了错误。当然,实际中我还没有发现这样的错误,但是理论上是可能的。 因此,避免从构造器中启动线程是一个明智的选择。

方式三、使用回调接口
如果一个以上的类对实例对结果计算结果感兴趣,则可以设计一个所有这些类都实现的接口,接口中声明回调的方法。

如果一个以上的对象对线程计算的结果感兴趣,则线程可以保存一个回调对象列表。特定对象可以通过调用Thread或Runnable类中的方法将自己加入到这个表中,从而注册为对计算结果标识的兴趣。

/**

* 回调接口

*

* @author leizhimin 2008-9-13 17:20:11

*/

public
interface
DigestListener {

public
void
digestCalculated(byte
digest[]);

}

/**

* Created by IntelliJ IDEA.

*

* @author leizhimin 2008-9-13 17:22:00

*/

public
class
ListCallbackDigest implements
Runnable {

private
File inputFile;

private
List<DigestListener> listenerList = Collections.synchronizedList(new
ArrayList<DigestListener>());

public
ListCallbackDigest(File inputFile) {

this
.inputFile = inputFile;

}

public
synchronized
void
addDigestListener(DigestListener ler) {

listenerList.add(ler);

}

public
synchronized
void
removeDigestListener(DigestListener ler) {

listenerList.remove(ler);

}

private
synchronized
void
sendDigest(byte
digest[]) {

for
(DigestListener ler : listenerList) {

ler.digestCalculated(digest);

}

}

public
void
run() {

try
{

FileInputStream in = new
FileInputStream(inputFile);

MessageDigest sha = MessageDigest.getInstance("MD5"
);

DigestInputStream din = new
DigestInputStream(in, sha);

int
b;

while
((b = din.read()) != -1) ;

din.close();

byte
[] digest = sha.digest(); //摘要码

//完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程

System.out.println(digest);

this
.sendDigest(digest);

} catch
(FileNotFoundException e) {

e.printStackTrace();

} catch
(NoSuchAlgorithmException e) {

e.printStackTrace();

} catch
(IOException e) {

e.printStackTrace();

}

}

}

/**

* Created by IntelliJ IDEA.

*

* @author leizhimin 2008-9-13 17:35:20

*/

public
class
ListCallbackDigestUser implements
DigestListener{

private
File inputFile; //回调与每个文件绑定

private
byte
digest[]; //文件的消息摘要码

public
ListCallbackDigestUser(File inputFile) {

this
.inputFile = inputFile;

}

/**

* 计算某个文件的消息摘要码

*/

public
void
calculateDigest(DigestListener dl) {

ListCallbackDigest callback = new
ListCallbackDigest(inputFile);

callback.addDigestListener(dl); // 注册监听器

Thread t = new
Thread(callback);

t.start();

}

public
void
digestCalculated(byte
digest[]) {

this
.digest = digest;

//将消息摘要码输出到控制台实际上执行的是this.toString()方法

System.out.println(this
);

}

/**

* 显示结果

*

* @return 结果

*/

public
String toString() {

String result = inputFile.getName() + ": "
;

if
(digest != null
) {

for
(byte
b : digest) {

result += b + " "
;

}

} else
{

result += "digest 不可用!"
;

}

return
result;

}

public
static
void
main(String[] args) {

String arr[] = {"C://xcopy.txt"
, "C://x.txt"
, "C://xb.txt"
, "C://bf2.txt"
};

args = arr;

for
(int
i = 0; i < args.length; i++) {

File f = new
File(args[i]);

ListCallbackDigestUser cb = new
ListCallbackDigestUser(f);

cb.calculateDigest(cb);

}

}

}

(注:最后个方式代码,由本人稍做了修改)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: