Java 从线程返回信息

习惯了传统使用Main单线程过程式模型的程序员在转向多线程环境时,最难掌握的就是如何从线程中获取返回信息。从结束的线程获得信息,这是多线程编程中最常误解的方面之一,run()方法和start()方法不返回任何值。接下来,我们逐一讲解几种获取返回信息的方法。整个程序设计,简单地显示SHA-256文件地址信息,文件线程需要把文件地址返回给执行主线程Main。

  • 最直接的方式:线程与信息显示同时进行

线程调用函数:

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

import com.gy.test.CallbackDigestUserInterface;

public class ReturnDigest extends Thread {
    private String filename;
    private byte[] digest;
    public ReturnDigest(String filename) {
        // TODO Auto-generated constructor stub
        this.filename = filename;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.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 (Exception e) {
            // TODO: handle exception
            System.err.println(e);
        }

    }


    public byte[] getDigest() {
        return digest;
    }
}

主线程:

import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;

public class ReturnDigestUserInterface {

    public static void main(String[] args) {
        String[] files = {"F:\\MySQL\\eclipse\\test.txt", 
                "F:\\MySQL\\eclipse\\test1.txt", 
                "F:\\MySQL\\eclipse\\test2.txt"};
        for (String filename: files) {
            ReturnDigest dr = new ReturnDigest(filename);
            dr.start();
            StringBuilder result = new StringBuilder(filename);
            System.out.println(result);
            result.append(": ");
            byte[] digest = dr.getDigest();
            result.append(DatatypeConverter.printHexBinary(digest));
            System.out.println(result);
        }
    }

}

输出结果:
这里写图片描述
结果显示,空指针错误。问题在于,主程序会在线程有机会初始化文件地址信息之前就获取并使用信息。在单线程中,main()函数调用dr.getDigest()方法之前结束,也可能没有结束,导致结果无法获取到,出现错误。

  • 竞态条件:一种可能方法把dr.getDigest()调用移动到main()方法

主代码:

import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;

public class ReturnDigestUserInterface {

    public static void main(String[] args) {
        String[] files = {"F:\\MySQL\\eclipse\\test.txt", 
                "F:\\MySQL\\eclipse\\test1.txt", 
                "F:\\MySQL\\eclipse\\test2.txt"};
        ReturnDigest[] digests = new ReturnDigest[files.length];
        for (int i = 0; i < files.length; i++) {
            digests[i] = new ReturnDigest(files[i]);
            digests[i].start();
        }       

        for (int i = 0; i < digests.length; i++) {
            StringBuilder result = new StringBuilder(files[i]);
            System.out.println(result);
            result.append(": ");
            byte[] digest = digests[i].getDigest();
            result.append(DatatypeConverter.printHexBinary(digest));
            System.out.println(result);
        }
    }

}

结果显示如第一种方法一样,如果你有幸的化是可以获取到结果,但是我没有获得过。。。

  • 轮询方式:获取方法返回一个标志位,直到获取到结果字段为止
    代码如下:
import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.ReturnDigest;

public class ReturnDigestUserInterface {

    public static void main(String[] args) {
        String[] files = {"F:\\MySQL\\eclipse\\test.txt", 
                "F:\\MySQL\\eclipse\\test1.txt", 
                "F:\\MySQL\\eclipse\\test2.txt"};
        ReturnDigest[] digests = new ReturnDigest[files.length];
        for (int i = 0; i < files.length; i++) {
            digests[i] = new ReturnDigest(files[i]);
            digests[i].start();
        }       

        for (int i = 0; i < digests.length; i++) {
            while (true) {
                byte[] digest = digests[i].getDigest();
                if (digest != null) {
                    StringBuilder result = new StringBuilder(files[i]);
                    System.out.println(result);
                    result.append(": ");
                    result.append(DatatypeConverter.printHexBinary(digest));
                    System.out.println(result);
                    break;
                }
            }
        }               
    }

}

结果显示:
这里写图片描述
可以看出,我们获取到了所得加密信息。但是这个解决方法不能保证一定能工作。在有些虚拟机上,主线程会占用所有可能用的时间,而没有给具体的工作线程流出任何时间。主线程太忙于检查工作的完成情况,以至于没有时间来具体完成任务。

  • 回调方法:通过调用主类中的一个方法,称为回调
    当线程的run()方法接近结束时候,要做的最后一件事情就是基于结果调用主程序中的一个已知方法。
    线程代码如下:
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;

import com.gy.test.CallbackDigestUserInterface;

public class CallbackDigest implements Runnable {
    private String filename;
    public CallbackDigest(String filename) {
        // TODO Auto-generated constructor stub
        this.filename = filename;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        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();
            byte[] digest = sha.digest();
            CallbackDigestUserInterface.receiveDigest(digest, filename);
        } catch (Exception e) {
            // TODO: handle exception
            System.err.println(e);
        }

    }

}

主函数代码如下:

import javax.xml.bind.DatatypeConverter;
import com.gy.returndigest.CallbackDigest;

public class CallbackDigestUserInterface {

    public static void receiveDigest(byte[] digest, String name) {
        StringBuilder result = new StringBuilder(name);
        System.out.println(result);
        result.append(": ");
        result.append(DatatypeConverter.printHexBinary(digest));
        System.out.println(result);
    }

    public static void main(String[] args) {
        String[] files = {"F:\\MySQL\\eclipse\\test.txt", 
                "F:\\MySQL\\eclipse\\test1.txt", 
                "F:\\MySQL\\eclipse\\test2.txt"};
        for (int i = 0; i < files.length; i++) {
            CallbackDigest cb = new CallbackDigest(files[i]);
            Thread thread = new Thread(cb);
            thread.start();
        }       

    }
}

结果显示:
这里写图片描述
我们可以看出程序是顺序加载不同的文件地址信息,然后顺序执行加密输出结果,达到使用线程顺序输出结果的目的。

  • Callable带返回值的线程运算
    我们也可以使用带有返回值的线程进行加密输出,这样就是可以保证获取一条信息,就进行加密一次。
    线程函数代码:
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.concurrent.Callable;
import javax.xml.bind.DatatypeConverter;

public class CallableThread implements Callable<String> {

    private String filename;
    private String str;

    public CallableThread(String filename) {
        // TODO Auto-generated constructor stub
        this.filename = filename;
    }


    @Override
    public String call() throws Exception {
        // TODO Auto-generated method stub
        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();
            StringBuilder result = new StringBuilder(filename);
            System.out.println(result);
            result.append(": ");
            result.append(DatatypeConverter.printHexBinary(digest));
            str = new String(result);
        } catch (Exception e) {
            // TODO: handle exception
            System.err.println(e);
        }       
        return str;
    }

}

主函数如下:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import com.gy.returndigest.CallableThread;

public class Main {

    public static void main(String[] args) {
        String[] files = {"F:\\MySQL\\eclipse\\test.txt", 
                "F:\\MySQL\\eclipse\\test1.txt", 
                "F:\\MySQL\\eclipse\\test2.txt"};

        for (int i = 0; i < files.length; i++) {
            CallableThread ct = new CallableThread(files[i]);
            FutureTask<String> ft = new FutureTask<>(ct);
            new Thread(ft, "返回值").start();
            try {
                System.out.println(ft.get());
            } catch (Exception e) {
                // TODO: handle exception
                System.out.println(e);
            }
        }
    }

}

结果如下所示:
这里写图片描述

总结

在顺序执行编程时候,顺序执行程序思想是想到的主要思想。但是多线程运行以后,由于主线程与子线程是并发运行,很难保障获得期望结果,所以深入了解并发线程思想,获取我们想获得的程序信息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值