运行线程
目前在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()。一旦两个线程都结束,将比较它们的结果,并返回最大值。