java多線程學習二

運行線程
目前在java的線程學習中,我學到的創建、運行線程的方法主要有三種。
方法一、第一種是直接構造Thread類的一個實例,調用它的start()方法。要讓線程完成一些操作,可以對Thread類派生子類,覆蓋其run()方法。
下面的例子源於書本,該程序用於計算多個文件的安全散列算法(SHA)的摘要。 DigestThread是Thread的子類,它的run()方法爲指定文件計算一個256位的
SHA-2消息摘要。爲此要用一個DigestInputStream來讀取這個文件。讀取結束時,可以從digest()方法種得到這個散列。
例子一:
package com.HL.DigestThread;

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

import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.DatatypeConverterInterface;

public class DigestThread extends Thread{
	private String fileName;
	public DigestThread(String fileName){
		this.fileName = fileName;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			FileInputStream inputStream = new FileInputStream(fileName);
			try {
				MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
				DigestInputStream digestInputStream = new DigestInputStream(inputStream, messageDigest);
				try {
					while(digestInputStream.read() != -1);
					digestInputStream.close();
					byte[]digest = messageDigest.digest();
					StringBuilder result = new StringBuilder(fileName);
					result.append(":");
					result.append(DatatypeConverter.printHexBinary(digest));
					System.out.println(result);
					
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			} catch (NoSuchAlgorithmException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		super.run();
	}
	public static void main(String[] args) {
		for(String filename :args){
			Thread t = new DigestThread(filename);
			t.start();
		}

		
	}
}
main()方法從命令行中讀取文件名,針對每個文件名啓動一個新的DigestThread。
注意,這裏Thread的派生子類,只應當覆蓋run()方法,而不應該覆蓋其它方法,例如start(),join()等標準方法。


方法二、爲了避免覆蓋Thread的標準方法,推薦編寫一個Runnable的實例,將線程需要 完成的任務包裝在run()中。
第二個例子將第一個例子改寫使用了runnable接口,把extends Thread改爲了implements Runnable,並在main()方法裏把DigestRunnable對象傳給了Thread的
構造函數,程序的基本邏輯沒有改變。

例子二:
package com.HL.DigestThread;

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

import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.DatatypeConverterInterface;

public class DigestThreadRunnable implements Runnable{
	private String fileName;
	public DigestThreadRunnable(String fileName){
		this.fileName = fileName;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			FileInputStream inputStream = new FileInputStream(fileName);
			try {
				MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
				DigestInputStream digestInputStream = new DigestInputStream(inputStream, messageDigest);
				try {
					while(digestInputStream.read() != -1);
					digestInputStream.close();
					byte[]digest = messageDigest.digest();
					StringBuilder result = new StringBuilder(fileName);
					result.append(":");
					result.append(DatatypeConverter.printHexBinary(digest));
					System.out.println(result);
					
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			} catch (NoSuchAlgorithmException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		super.run();
	}
	public static void main(String[] args) {
		for(String filename :args){
			DigestThreadRunnable dr = new DigestThreadRunnable(filename);
			Thread thread  = new Thread(dr);
			thread.start();		
		}
		
	}
}


方法三:Executor和callable創建線程。這裏涉及到一個知識點,如何從線程返回信息,這是多線程編程中最常被誤解的方面之一。run()和start()方法本身不返回任何值,,大多數人的第一個反應是把結果存儲在一個字段中,再提供一個獲取方法,如下面的例子所示。
	public byte[] getDigest() {
		return digest;
	}
		

在主程序中使用存取方法取得線程輸出
package com.HL.DigestThread;

import javax.xml.bind.DatatypeConverter;

public class ReturnDigestTest {

	public static void main(String[] args) {
		for(String fileName : args){
			DigestThread dt = new DigestThread(fileName);
			dt.start();
			
			//顯示結果
			StringBuilder result = new StringBuilder(fileName);
			result.append(":");
			byte[]digest  = dt.getDigest();
			result.append(DatatypeConverter.printHexBinary(digest));
			System.out.println(result);
		}
	}
}

但這樣做是存在問題的,我們可能得不到正確的輸出。因爲dt.start()啓動的計算可能在dt.getDigest()之前還沒有結束,也可能結束了,主線程是不會等子線程的。如果還沒有結束,dt.getDigest()則會返回null,此時訪問digest會拋出異常。

Java5 引入了多線程編程的一個新方法,通過隱藏細節可以更容易的處理回調。我們不再需要直接創建一個線程,是需要創建一個ExecutorService,它會根據你的需要創建線程,可以向ExecutorService提交Callable任務,對於每個Callable任務,會分別得到一個Future,之後可以向Future請求得到任務的結果。

下面這個例子,用於找出一個數字數組中的最大值,將一個任務分配到了兩個線程中運行,這樣比單線程運行快不少。 

例子二:
package com.HL.callableTest;

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 ) {
		// TODO Auto-generated constructor stub
		this.data = data;
		this.start = start;
		this.end = end;
	}
	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int max = Integer.MIN_VALUE;
		for(int i = start; i < end; i++){
			if(data[i] > max) max = data[i];
		}
		return max;
	}
}


package com.HL.callableTest;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadMaxFinder {
	public static int max(int[]data) throws InterruptedException, ExecutionException {
		if(data.length == 1)return data[0];
		
		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());
		
	}
	public static void main(String[] args) {
		try {
			System.out.println(String.valueOf(max(new int[]{12,5,16})));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

在最後一句Math.max(future1.get(), future2.get())中,調用future1.get()時,這個方法會阻塞,只有當第一個FindMaxTask結束時,纔會調用future2.get()。一旦兩個線程都結束,將比較它們的結果,並返回最大值。


發佈了30 篇原創文章 · 獲贊 19 · 訪問量 6995
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章