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()方法,這個方法可能在邏輯上認爲它屬於一個構造器。然而,在構造器中啓動線程是至關危險的,特別是對開始對象回調的線程。這裏存在一個競爭條件:構造器中假若有不少的事情要作,而啓動新的線程先作了,計算完成了後要回調,但是這個時候這個對象尚未初始化完成,這樣就產生了錯誤。固然,實際中我尚未發現這樣的錯誤,可是理論上是可能的。 所以,避免從構造器中啓動線程是一個明智的選擇。
方式3、使用回調接口
若是一個以上的類對實例對結果計算結果感興趣,則能夠設計一個全部這些類都實現的接口,接口中聲明回調的方法。
若是一個以上的對象對線程計算的結果感興趣,則線程能夠保存一個回調對象列表。特定對象能夠經過調用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() {
ListCallbackDigest callback =
new ListCallbackDigest(inputFile);
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(); } } }