在线程异步的场合下,如何将线程信息传递到调用处

本篇我们来学习一下Java是如何获取线程的信息然后返回到调用线程处(学习书籍(Java网络编程)):

1.首先,我们来学习一个简单的线程,继承Thread类,然后输出文件的摘要信息

public class DigestThread extends Thread {
	private File input;
// 通过构造方法,我们将file对象传递到run方法
	public DigestThread(File input) {
		this.input = input;
	}
	
	public void run() {
		try {
			FileInputStream in = new FileInputStream(this.input);
			// 消息摘要
			MessageDigest sha = MessageDigest.getInstance("SHA");
			DigestInputStream din = new DigestInputStream(in, sha);
			
			// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
			while ((din.read()) != -1);
			din.close();
			
			byte [] digest = sha.digest();
			
			StringBuffer result = new StringBuffer(input.toString());
			result.append(": ");
			for (int i = 0; i < digest.length; i++) {
				result.append(digest[i] + " ");
			}
			
			// 输出
			System.out.println(result.toString());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread thread = new DigestThread(new File("D:\\test\\demo01.html"));
		thread.start();
	}

}

 2.接下来,我们将以上内容修改为实现Runnable接口形式

package network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class DigestRunnable implements Runnable {
	private File input;
	public DigestRunnable(File input) {
		this.input = input;
	}
	
	public void run() {
		try {
			FileInputStream in = new FileInputStream(this.input);
			// 消息摘要
			MessageDigest sha = MessageDigest.getInstance("SHA");
			DigestInputStream din = new DigestInputStream(in, sha);
			
			// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
			while ((din.read()) != -1);
			din.close();
			
			byte [] digest = sha.digest();
			
			StringBuffer result = new StringBuffer(input.toString());
			result.append(": ");
			for (int i = 0; i < digest.length; i++) {
				result.append(digest[i] + " ");
			}
			
			// 输出
			System.out.println(result.toString());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		// 此处将DigestRunnable传递到Thread的构造方法中
		Thread thread = new Thread(new DigestRunnable(new File("D:\\test\\demo01.html")));
		thread.start();
	}

}

 3.单线程和多线程让程序员棘手的问题就是如何将线程的信息传递回线程的调用处,在以上实例中,我们只是把线程中获取的消息摘要输出,假如说我们要把摘要返回到主线程调用处,我们该怎么做呢。多数情况下,我们可以进行了一下操作,使用变量存储

3.1.线程类

	private File input;
	public ReturnDigestThread(File input) {
		this.input = input;
	}
	
	// 将摘要存储到该变量中,使用get set方法进行获取
	private byte [] digest;
	
	public void run() {
		try {
			FileInputStream in = new FileInputStream(this.input);
			// 消息摘要
			MessageDigest sha = MessageDigest.getInstance("SHA");
			DigestInputStream din = new DigestInputStream(in, sha);
			
			// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
			while ((din.read()) != -1);
			din.close();
			
			// 设置到results变量中
			setDigest(sha.digest());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public byte[] getDigest() {
		return digest;
	}

	public void setDigest(byte[] digest) {
		this.digest = digest;
	}

 3.2.主线程类

public static void main(String[] args) {

		File input = new File("D:\\test\\demo01.html");
		ReturnDigestThread thread = new ReturnDigestThread(input);
		thread.start();

		// 输出
		StringBuffer result = new StringBuffer(input.toString());
		result.append(": ");
		
		// 通过对象的变量获取线程的执行结果
		for (int i = 0; i < thread.getDigest().length; i++) {
			result.append(thread.getDigest()[i] + " ");
		}
		System.out.println(result.toString());
	}

 此时运行主程序,我们得到的结果会出现以下错误

错误 写道 Exception in thread "main" java.lang.NullPointerException
at network.ReturnDigestUserInterface.main(ReturnDigestUserInterface.java:18)

 从程序的顺序上看,我们先调用线程获取了摘要,然后才输出摘要,应该没问题,但是问题在于,主线程在线程有机会初始化摘要digest变量之前就可能使用digest变量,导致出现了空值错误。

4.以上问题发生的根本原因就是主线程和子线程存在竞争关系,那么我们可能采用以下方式进行避免

修改主线程 写道 public static void main(String[] args) throws InterruptedException {

File input = new File("D:\\test\\demo01.html");
ReturnDigestThread thread = new ReturnDigestThread(input);
thread.start();

// 这样做只是可能会得到了我们想要的结果,但是,依然是错误的做法
Thread.sleep(100);

// 输出
StringBuffer result = new StringBuffer(input.toString());
result.append(": ");

// 通过对象的变量获取线程的执行结果
for (int i = 0; i < thread.getDigest().length; i++) {
result.append(thread.getDigest()[i] + " ");
}
System.out.println(result.toString());
}

 以上方式,通过控制红色后面代码执行时间差来获取正确的结果,但是是非常糟糕的做法。

5.那么接下来我们可能会采用轮询的方式

修改主线程 写道 public static void main(String[] args) throws InterruptedException {

File input = new File("D:\\test\\demo01.html");
ReturnDigestThread thread = new ReturnDigestThread(input);
thread.start();

// 输出
StringBuffer result = new StringBuffer(input.toString());
result.append(": ");

// 进行轮询
int count = 0;
while (true) {
System.out.println(count++);
// 如果摘要为空,则进行获取
byte[] digest = thread.getDigest();
if (digest != null) {
for (int i = 0; i < digest.length; i++) {
result.append(digest[i] + " ");
}
System.out.println(result.toString());
// 然后退出循环
break;
}
}
}

 请注意以下输出结果

结果 写道 ...
659
660
661
662
663
664
665
666
667
D:\test\demo01.html: 84 56 116 -95 -96 -119 -123 -19 117 -125 22 48 53 13 -111 125 106 -66 44 69

 以上方式下,我们做了667次无用功,这是多么的可怕,虽然我们最终会得到正确的摘要信息,但是这种方式,让我们伤透了

6.那么还有更好的解决方式吗,有,使用回调方式,即在子线程得到结果后,调用主类的方法进行参数的输出

子线程 写道 package network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class CallbackDigestThread extends Thread {
private File input;
public CallbackDigestThread(File input) {
this.input = input;
}

public void run() {
try {
FileInputStream in = new FileInputStream(this.input);
// 消息摘要
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestInputStream din = new DigestInputStream(in, sha);

// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
while ((din.read()) != -1);
din.close();

// 回调主线程方法进行输出结果
CallbackDigestUserInterface.reciveDigest(sha.digest(), this.input.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

 

主线程 写道 package network;

import java.io.File;

public class CallbackDigestUserInterface {

public static void main(String[] args) throws InterruptedException {
File input = new File("D:\\test\\demo01.html");
Thread thread = new CallbackDigestThread(input);
thread.start();
}

/**
* 通过提供该方法到子线程,子线程在得到digest后,回调该方法输出.
*
* @param digest
* @param name
*/
public static void reciveDigest(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.toString());
}

}

 通过以上方式我们很好的解决了问题,main主线程只是启动了子线程,而使用digest的地方放置在另外一个方法reciveDigest中,实质上,reciveDigest方法是在子线程中执行。

7.以上使用了静态方法,那么多数情况下,我们的项目只有一个主线程,那么多数情况下,我们需要使用实例对象方法进行回调

子线程 写道 package network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class InstanceCallbackDigestThread extends Thread {
private File input;
private InstanceCallbackDigestUserInterface callback;

// 通过构造方法,我们将实例对象传递过来
public InstanceCallbackDigestThread(File input, InstanceCallbackDigestUserInterface callback) {
this.input = input;
this.callback = callback;
}

public void run() {
try {
FileInputStream in = new FileInputStream(this.input);
// 消息摘要
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestInputStream din = new DigestInputStream(in, sha);

// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
while ((din.read()) != -1);
din.close();

// 回调主线程方法进行输出结果
主类 写道 package network;

import java.io.File;

public class InstanceCallbackDigestUserInterface {

public static void main(String[] args) throws InterruptedException {
File input = new File("D:\\test\\demo01.html");
InstanceCallbackDigestUserInterface callback = new InstanceCallbackDigestUserInterface();

// 将实例对象传递到子线程中
Thread thread = new InstanceCallbackDigestThread(input, callback);
thread.start();
}

/**
* 通过提供该方法到子线程,子线程在得到digest后,回调该方法输出.
*
* @param digest
* @param name
*/
public void reciveDigest(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.toString());
}

}  
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

 相对于轮询机制,以上回调方式更灵活一点,那么接下来,我们会展示另外一个方式listener

listener 写道 package network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class ListCallbackDigestRunnable implements Runnable {
private File input;
private DigestListener listener;

public ListCallbackDigestRunnable(File input) {
this.input = input;
}

private void sendDigest(byte[] digest) {
listener.digestCalculdated(digest);
}

public void run() {
try {
FileInputStream in = new FileInputStream(this.input);
// 消息摘要
MessageDigest sha = MessageDigest.getInstance("SHA");
DigestInputStream din = new DigestInputStream(in, sha);

// 要完成消息摘要计算,先要调用此摘要输入流的一个 read 方法,之后在关联的消息摘要上调用一个 digest 方法。
// read方法会因为文件的大小而阻塞
while ((din.read()) != -1)
;
din.close();

byte[] digest = sha.digest();

// 当前线程获取到当前文件的digest后,会通过listener将摘要信息通过主线程的接口方法进行输出.
this.sendDigest(digest);

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public DigestListener getListener() {
return listener;
}

public void setListener(DigestListener listener) {
this.listener = listener;
}
}

 

主程序 写道 package network;

import java.io.File;

public class ListCallbackDigestUserInterface implements DigestListener {

private File input;

public ListCallbackDigestUserInterface(File input) {
this.input = input;
}

/**
* 启动不同的线程,对消息摘要进行处理
*/
private void digestCalculated() {
ListCallbackDigestRunnable cb = new ListCallbackDigestRunnable(input);
// 为当前线程设置自己的listener
cb.setListener(this);

Thread t = new Thread(cb);
t.start();
}

public static void main(String[] args) throws InterruptedException {
// 进行三个文件的摘要信息输出
String[] filenames = { "D:\\test\\demo01 - 副本.html", "D:\\test\\demo01 - 副本 (2).html",
"D:\\test\\demo01.html" };
for (String filename : filenames) {
File input = new File(filename);
// 创建当前对象实例
ListCallbackDigestUserInterface d = new ListCallbackDigestUserInterface(input);
d.digestCalculated();
}
}

@Override
public void digestCalculdated(byte[] digest) {
// 输出
StringBuffer result = new StringBuffer(input.toString());
result.append(": ");

for (int i = 0; i < digest.length; i++) {
result.append(digest[i] + " ");
}
System.out.println(result.toString());
}

}

 

接口 写道 package network;

public interface DigestListener {

/**
* 对摘要进行处理.
*
* @param digest
*/
public void digestCalculdated(byte [] digest);
}

 以上方式通过对不同的文件建立线程获取各自的摘要digest后,通过在线程中注册的listener对象调用接口方法进行输出。

 

好了,以上介绍的各种方式,希望对各位有所帮助,谢谢。

上一篇:《读者文摘》2014-2021年电子版合集 | Reader's Digest


下一篇:php实现大文件上传带进度条