`
qing_gee
  • 浏览: 121885 次
  • 性别: 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  
好难

相关推荐

    qt线程传递参数.7z

    "qt线程传递参数.7z"这个压缩包很可能包含了关于如何在Qt中进行线程间参数传递的示例代码或教程。Qt提供了QThread类来支持线程操作,并且提供了一些机制来安全地在不同线程之间传递数据。 首先,我们了解下Qt中的...

    TimeSetEvent VB多线程例程

    需要注意的是,使用API函数需要先进行声明,如上所示,将函数原型导入到VB环境中。此外,由于`TimeSetEvent`来自`winmm.dll`库,所以还需要确保项目链接了这个库。 多线程编程能够提高程序的并发性和响应性,但同时...

    windows异步串口读写(高可靠)

    在同步模式下,程序会阻塞等待数据传输完成,这可能导致应用程序的响应速度变慢,特别是在处理大块数据或需要频繁交互的场合。而异步模式则允许程序在数据传输过程中继续执行其他任务,提高系统资源利用率,同时保证...

    invoke和beginInvoke

    - 在多线程环境中,委托允许我们在一个线程中调用另一个线程上的方法,从而解决线程同步问题。 2. **invoke的使用**: - Invoke是控件(如WinForm或WPF中的控件)提供的一种方法,用于在控件创建的线程(通常为UI...

    C++内联汇编示例

    在某些情况下,内联汇编可以用来优化递归函数,减少栈空间的使用,或者配合多线程库,实现线程间的直接通信,从而提高效率。 最后,"Less3_while"可能涉及到循环结构的实现。在C++中,循环可以通过for、while等...

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

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

    RT-Thread 嵌入式操作系统中文指导手册

    - **程序的运行上下文**:线程切换时需要保存当前线程的上下文信息,并加载下一个线程的上下文。 - **线程设计**:应遵循良好的编程实践,例如避免长时间占用CPU,合理使用同步机制等。 #### 四、定时器 **定时器*...

    IOCP.zip_IOCP

    IOCP的核心原理是利用操作系统的内核对象,将I/O操作的完成通知从内核空间传递到用户空间,从而减少线程上下文切换的开销,提高系统在高并发环境下的性能。** 在Windows中,IOCP通过CreateIoCompletionPort函数创建...

    DLL(包括动态和静态库,还有一个回调函数)

    这种模式在需要高度定制或异步通信的场合非常常见,例如游戏引擎、图形库和网络库等。 创建和使用DLL的过程主要包括以下几个步骤: 1. **定义接口**:在DLL和调用程序之间定义一个公共接口,包括导出函数声明和...

    回调函数的使用[文].pdf

    - C/C++支持函数指针,因此可以将函数地址作为参数传递给其他函数,被调用者在适当的时机通过这个指针调用回调函数。这种模式广泛应用于处理数据结构(如遍历树)或者需要自定义行为的场合。 4. **Windows回调函数...

    VB各领域程序之Callback

    Callback机制允许一个函数或过程将另一个函数或过程的地址作为参数传递,然后在适当的时候由接收者调用这个传递的函数或过程。这种设计模式在各种编程领域都有广泛的应用,如事件处理、异步编程和用户自定义功能等。...

    java 面试题

    从给定的文件信息中,我们可以提炼出一系列与Java编程语言相关的面试知识点,涉及范围广泛,包括基础概念、数据类型、异常处理、集合框架、线程安全、对象模型以及高级特性如EJB和Servlet等。下面将对这些知识点进行...

    开源利器ACE

    主动对象模式涉及到 ACE_Task, ACE_Method_Object, ACE_Future 以及 ACE_Future_Observer 类,该模式很灵活,既适用于同步调用函数的场合,也适用于异步调用函数的场合或有回调接口的场合。 Singleton 模式是对 ...

    java面试宝典2012版

    如果没有处理异常的catch块,则异常会向上传递到方法调用链,直到main方法中也没有捕获,则程序终止,并打印异常信息。 44. 常见的runtimeexception? 常见的RuntimeException包括NullPointerException、...

    c语言版的类似qq源码

    在这个类似QQ的程序中,可能在服务器端创建了多个线程来处理不同的客户端连接,或者在客户端使用线程实现异步操作,如接收消息的同时发送消息。 3. **套接字(Socket)编程**:套接字是网络通信中的一个概念,是...

    一键锁屏(源码)

    使用Intent可以在不同组件之间传递信息。在这个场景下,可能会创建一个Intent来触发锁屏操作,并设置相应的IntentFilter以便BroadcastReceiver能够捕获。 7. **异步操作**: 为了保证用户体验,锁屏操作应该在一...

    callback_sigslot

    回调更适合简单、灵活的场景,而信号槽在大型项目中,特别是在需要对象间通信和异步处理的场合,其优势更为明显。在实际开发中,开发者可以根据项目的规模、复杂性和团队的习惯来选择合适的机制。了解并熟练掌握这两...

    基于软中断的进程间通信的设计

    在进程间通信中,软中断可以用来同步、通知或者传递数据,尤其是在需要快速响应的场合。 在实现软中断通信时,需要注意以下几个关键点: 1. **中断处理程序**:必须正确注册和处理软中断,确保其在适当的时间被...

    方法回调模式

    回调模式广泛应用于各种场合,特别是在需要异步处理或事件驱动的情况下。例如,在GUI应用开发中,用户界面组件经常需要响应用户的操作(如按钮点击),这些操作通常通过回调函数实现。在多线程或网络编程中,回调也...

    PHP高级编程之消息队列.pdf

    而消息队列可以采用同步或异步的方式,并能传递用户自定义的无规则协议,实现存储转发功能。 使用消息队列的场合和时机通常与具体需求相关。同步需求更适合采用RPC,而异步需求更倾向于使用消息队列。消息队列用于...

Global site tag (gtag.js) - Google Analytics