Returning Info from a Thread
方案一
class DigestThread extends Thread{
private String filename;
private byte[] digest;
public DigestThread(String fileName){
this.filename = fileName;
}
@Override
public void run() {
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ;
din.close();
digest = sha.digest();
} catch (IOException ex) {
System.err.println(ex);
} catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public byte[] getDigest(){
return digest;
}
}
public static void main(String[] args) {
for (String filename : args) {
// Calculate the digest
DigestThread dr = new DigestThread(filename);
dr.start();
// Now print the result
StringBuilder result = new StringBuilder(filename);
result.append(": ");
byte[] digest = dr.getDigest();
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
}
输出会报异常“NullPointerException”,因为getDigest()时,digest还未被初始化,也就是那个线程还没有执行完毕。
方案二:
public static void main(String[] args) throws Exception {
DigestThread[] digests = new DigestThread[args.length];
for(int i=0;i<args.length;i++){
digests[i] = new DigestThread(args[i]);
digests[i].start();
}
for(int i=0;i<args.length;i++){
StringBuilder result = new StringBuilder(args[i]);
result.append(": ");
byte[] digest = digests[i].getDigest();
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
}
如果幸运,会得到想要的结果,但很可能还会得到空指针异常。因为主线程去getDigest时,子线程还未完成。
你能获得的是正常的结果,还是异常还是程序被挂起,依赖于很多因素,包括程序衍生了多少个线程,当前系统的cup和disk的运行速度,系统用了多少cpu,jvm对不同线程分配时间的算法。这叫做“race condition”。
方案三:
public static void main(String[] args) throws Exception {
DigestThread[] digests = new DigestThread[args.length];
for(int i=0;i<args.length;i++){
digests[i] = new DigestThread(args[i]);
digests[i].start();
}
for(int i=0;i<args.length;i++){
while(true){
byte[] digest = digests[i].getDigest();
if(digest != null){
StringBuilder result = new StringBuilder(args[i]);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
break;
}
}
}
}
菜鸟一般都这么搞,这样做有时也能完成工作,但输出顺序完全按线程启动的顺序,而“忽略、屏蔽”了不同线程间速度的差异,显然,效率上差,做了无用功。但在一些虚拟机上,主线程占用了所有可用时间,而没有时间留给子线程去工作,那在这样的情况下,就不行了。
下面的方案是我应用观察者模式写的:
class DigestTask extends Observable implements Runnable{
private String filename;
private byte[] digest;
public DigestTask(String fileName){
this.filename = fileName;
}
@Override
public void run() {
try {
System.out.println("DigestTask "+filename+"-"+Thread.currentThread().getName());
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1) ;
din.close();
digest = sha.digest();
this.setChanged();
this.notifyObservers();
} catch (IOException ex) {
System.err.println(ex);
} catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
public byte[] getDigest(){
return digest;
}
public String getFileName(){
return filename;
}
}
public static void main(String[] args) throws Exception {
DigestTask[] digests = new DigestTask[args.length];
for(int i=0;i<args.length;i++){
digests[i] = new DigestTask(args[i]);
digests[i].addObserver(new Observer() {
@Override
public void update(Observable o, Object arg) {
outputDigest(((DigestTask)o).getFileName(), ((DigestTask)o).getDigest());
}
});
Thread t = new Thread(digests[i]);
t.start();
}
}
static void outputDigest(String filename,byte[] digest){
StringBuilder result = new StringBuilder(filename);
result.append(": ");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println("outputDigest "+filename+"-"+Thread.currentThread().getName());
System.out.println(result);
}
这样只是客户端可以自定义当子线程执行完成后的行为, 本质上还是没有从执行的线程返回数据到主线程。
但接下来作者的解决方案是“CallBack”。“Observer Patern”,一般的callback可以设置一个回调对象,观察者呢可以设置一组,所有对该内容变化感兴趣的callback。
class InstanceCallbackDigest implements Runnable {
private String filename;
private InstanceCallbackDigestUserInterface callback;
public InstanceCallbackDigest(String filename,
InstanceCallbackDigestUserInterface callback) {
this.filename = filename;
this.callback = callback;
}
@Override
public void run() {
try {
FileInputStream in = new FileInputStream(filename);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
DigestInputStream din = new DigestInputStream(in, sha);
while (din.read() != -1)
; // read entire file
din.close();
byte[] digest = sha.digest();
callback.receiveDigest(digest);
} catch (Exception ex) {
System.err.println(ex);
}
}
}
class InstanceCallbackDigestUserInterface {
private String filename;
private byte[] digest;
public InstanceCallbackDigestUserInterface(String filename) {
this.filename = filename;
}
public void calculateDigest() {
InstanceCallbackDigest cb = new InstanceCallbackDigest(filename, this);
Thread t = new Thread(cb);
t.start();
}
void receiveDigest(byte[] digest) {
this.digest = digest;
System.out.println(this);
}
@Override
public String toString() {
String result = filename + ": ";
if (digest != null) {
result += DatatypeConverter.printHexBinary(digest);
} else {
result += "digest not available";
}
return result;
}
public static void main(String[] args) {
for (String filename : args) {
// Calculate the digest
InstanceCallbackDigestUserInterface d = new InstanceCallbackDigestUserInterface(
filename);
d.calculateDigest();
}
}
}
一个单独的类可以当作一个数据结构!
Futures,Callables,Executors
jdk5 提供了方便的使用多线程的方式ExecutorService,接受Callable的实现类,针对每一个Callable都会返回一个Future,通过Future可以获得线程执行的结果,如果线程还未执行完毕,那调用线程会block,直到有结果,如果有结果,那就会立即返回。
假设现在有个任务,在一个数组中找出最大的数,可以用2个线程在做,基本上速度会是单线程的2倍。
class MultithreadedMaxFinder {
public static int max(int[] data) throws InterruptedException,
ExecutionException {
if (data.length == 1) {
return data[0];
} else if (data.length == 0) {
throw new IllegalArgumentException();
}
// split the job into 2 pieces
FindMaxTask task1 = new FindMaxTask(data, 0, data.length / 2);
FindMaxTask task2 = new FindMaxTask(data, data.length / 2, data.length);
// spawn 2 threads
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Integer> future1 = service.submit(task1);
Future<Integer> future2 = service.submit(task2);
return Math.max(future1.get(), future2.get());
}
}
class FindMaxTask implements Callable<Integer> {
private int[] data;
private int start;
private int end;
FindMaxTask(int[] data, int start, int end) {
this.data = data;
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
int max = Integer.MIN_VALUE;
for (int i = start; i < end; i++) {
if (data[i] > max)
max = data[i];
}
return max;
}
}
注意:调用future1.get()时,如果有结果则立即返回,如果还无结果,则block,等着,直到有结果了,future2.get()也是如此。