c3 Threads - Returning Infomation From a Thread

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()也是如此。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值