`
qing_gee
  • 浏览: 118774 次
  • 性别: Icon_minigender_1
  • 来自: 河南
社区版块
存档分类
最新评论

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

    博客分类:
  • Java
阅读更多

本篇我们来学习一下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对象调用接口方法进行输出。

 

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

1
0
分享到:
评论
1 楼 995998760 2014-06-21  
好难

相关推荐

    超级有影响力霸气的Java面试题大全文档

    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。 20、abstract class和interface有什么区别? ...

    java面试宝典

    21、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 10 22、我们在web 应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,...

    java 面试题 总结

    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。 17、abstract class和interface有什么区别? 声明方法...

    千方百计笔试题大全

    21、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 10 22、我们在web 应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,...

    java基础题 很全面

    14. 同步和异步有何异同,在什么情况下分别使用他们?举例说明。 9 15. abstract class和interface有什么区别? 9 16. heap和stack有什么区别。 9 17. Static Nested Class 和 Inner Class的不同。 9 18. 什么时候用...

    坚持学习WF

    坚持学习WF(18):使用IPendingWork接口 WF会定期在各个持久性点(Persistence Point)检查并将工作流实例保存到持久化存储中,这样如果工作流出现错误或是异常终止时相关的信息就会被存储,下次加载工作流实例时就会...

    坚持学习WF,WF学习教程

    WF会定期在各个持久性点(Persistence Point)检查并将工作流实例保存到持久化存储中,这样如果工作流出现错误或是异常终止时相关的信息就会被存储,下次加载工作流实例时就会从此做为开始点。这个主要是针对工作内置...

    最新Java面试宝典pdf版

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    Java面试宝典2010版

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 49. 下面两个方法同步吗?(自己发明) 50、多线程有几种实现方法?同步有几种实现方法? 51、启动一个线程是用run()还是start()? . 52、当一个...

    Java面试笔试资料大全

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    Java面试宝典2012版

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 ...

    Java面试宝典2012新版

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    JAVA面试宝典2010

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    Java面试宝典-经典

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    java面试题大全(2012版)

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 32 49. 下面两个方法同步吗?(自己发明) 33 50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、...

    java面试宝典2012

    48、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 36 49. 下面两个方法同步吗?(自己发明) 36 50、多线程有几种实现方法?同步有几种实现方法? 36 51、启动一个线程是用run()还是start()? . 37 52、...

Global site tag (gtag.js) - Google Analytics