2. 从线程返回信息
run()和start()方法都是没有返回值也没有输入参数的。那如何返回参数就是一个比较麻烦的事情。
最直接的想法是建立一个私有的字段,然后通过get之类的方法访问,但是这样的思路会有一个基本的问题就是竞态条件:
race condition:竞态条件,能否得到正确的结果依赖于进程的相对速度,而你无法控制这一点。
否定了这样直接的方法后,还有一种新手常用的获取数据的方法。也就是轮询:
轮询:在完成子进程的任务后,通过设定一个标志位(或者可能抛出一个异常)。然后在主进程中来定期询问标志位。
轮询有很多缺点:
- 轮询消耗了大量的cpu来做毫无意义的事情。
- 轮询不能保证一定完成任务,因为cpu可能给主进程分配更多的cpu时间。
实际上经典的解决方案是回调:
2.1回调
简单的说回调就是让子线程通知主线程。通过调用主线程中的一个方法做到。这被称为回调。
在子线程完成任务的时候调用主线程,主线程就可以在等待线程结束期间休息,而不会占用运行线程的时间。
回调机制相比于轮询机制有很多优点:
- 不会浪费太多的cpu周期
- 可以处理涉及更多的线程、对象和类更复杂情况。
对第二条做一些简单的说明:
这里其实可以使用观察者模式:
如果有多个对象对线程的计算结果感兴趣,那么线程就要维护一个回调对象的列表
特定的对象通过Thread或者Runnable类的一个方法把自己添加到这个列表中完成注册。
如果有多个类的实例对线程的计算结果感兴趣,那么就定义一个接口,所有这些类都实现新接口
通过新接口完成注册。
实现回调通常来说有两种方案:
- 一种是通过静态方法完成回调
- 一种是通过主线程的引用来完成回调。
通常来说后者优于前者。
- 主类的各个实例只映射至一个文件,可以自然地跟踪记录这个文件的信息。而不需要额外的数据结构。
- 这个实例可以很容易的在必要的时候可以很容易地重新计算某个特定文件的摘要。
但是有一点需要特别注意:
- 这里需要增加一个启动线程的方法,但是这个不属于构造函数,因为如果在构造函数中启动另一个子线程,而子线程又将回调原来的函数的时候,可能会在主类还没有完成初始化等工作的时候回调。这又引起了竞态条件,当然这里的例子没有初始化不会出现这样的问题,但是难以保障其他情况不出现竞态条件,所有请额外多写一个方法启动线程。
比如还是之前的例子:
通过静态方法完成回调:
package thread;
import java.util.ArrayList;
import javax.xml.bind.DatatypeConverter;
public class CallbackDigestUserInterface {
public static void receiveDigest(byte[] digest, String name){
StringBuilder result = new StringBuilder(name);
result.append(":");
result.append(DatatypeConverter.printHexBinary(digest));
System.out.println(result);
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test1.txt");
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test2.txt");
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test3.txt");
for(String filename:list){
CallbackDigest dr = new CallbackDigest(filename);
Thread t = new Thread(dr);
t.start();
}
}
}
package thread;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
public class CallbackDigest implements Runnable {
private String filename;
public CallbackDigest(String filename) {
// TODO Auto-generated constructor stub
this.filename = filename;
}
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();
byte[] digest = sha.digest();
CallbackDigestUserInterface.receiveDigest(digest, filename);
}catch(IOException ex){
System.err.println(ex);
}catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}
通过主线程的引用完成回调:
package thread;
import java.util.ArrayList;
import javax.xml.bind.DatatypeConverter;
public 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();
}
public 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) {
ArrayList<String> list = new ArrayList<String>();
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test1.txt");
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test2.txt");
list.add("E:\\tomcat\\webapps\\network\\bin\\thread\\test3.txt");
for(String filename:list){
InstanceCallbackDigestUserInterface dr = new InstanceCallbackDigestUserInterface(filename);
dr.calculateDigest();
}
}
}
package thread;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class InstanceCallbackDigest implements Runnable {
private String filename;
private InstanceCallbackDigestUserInterface callback;
public InstanceCallbackDigest(String filename,
InstanceCallbackDigestUserInterface callback) {
this.filename = filename;
this.callback = callback;
}
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();
byte[] digest = sha.digest();
callback.receiveDigest(digest);
}catch(IOException ex){
System.err.println(ex);
}catch (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
}
2.2 java中的回调实现
利用java的API不再需要重头创建线程,而是可以利用ExecutorService,它会根据需要为你创建线程。
可以向ExecutorService提交Callable任务,对于每个Callable任务,会分别得到一个Future。
之后可以向Future请求得到任务的结果。
例子:这里找出一个很大的数字数组中的最大值。
package thread;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.Test;
public class MultithreadMaxFinder {
private int j;
public static int max(int[] data) throws InterruptedException, ExecutionException{
if(data.length==1){
return data[0];
}else if(data.length==0){
throw new IllegalArgumentException();
}
FindMaxTask task1 = new FindMaxTask(data,0,data.length/2);
FindMaxTask task2 = new FindMaxTask(data,data.length/2,data.length);
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Integer> future1 = service.submit(task1);
Future<Integer> future2 = service.submit(task2);
return Math.max(future1.get(), future2.get());
}
@Test
public void test() throws InterruptedException, ExecutionException{
int[] list = new int[100];
Random random = new Random();
for(int i = 0;i <100;i++)
list[i]=random.nextInt(100)+1;
int a =max(list);
System.out.println(a);
}
}
package thread;
import java.util.concurrent.Callable;
public class FindMaxTask implements Callable<Integer> {
private int[] data;
private int start;
private int end;
public FindMaxTask(int[] data, int start, int end) {
this.data = data;
this.start = start;
this.end = end;
}
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;
}
}