如何从线程返回信息——轮询、回调、Callable

  考虑有这样一个LiftOff类:java

/**
 * 类LiftOff.java的实现描述:显示发射以前的倒计时
 * 
 * @author wql 2016年9月21日 下午1:46:46
 */
public class LiftOff implements Runnable {

    public LiftOff(){
        taskCount++;// 计数自增
    }

    private int        countDown = 3;        // 倒计时数字

    private static int taskCount = 0;

    private int        id        = taskCount;

    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("线程编号" + id + "--倒计时" + countDown);
            countDown--;
            Thread.yield();
        }
    }
}

  以及一个发射主线程:编程

public class Launch {

    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        Thread t = new Thread(liftOff);
        t.start();
        System.out.println("发射!");
    }
}

  咱们的本意是先显示倒计时,而后显示“发射!”,运行结果倒是设计模式

发射!
线程编号0--倒计时3
线程编号0--倒计时2
线程编号0--倒计时1
线程编号0--倒计时0

  由于main()函数也是一个线程,程序可否获得正确的结果依赖于线程的相对执行速度,而咱们没法控制这一点。想要使LiftOff线程执行完毕后再继续执行主线程,比较容易想到的办法是使用轮询多线程

/**
 * 类LiftOff.java的实现描述:显示发射以前的倒计时
 * 
 * @author wql 2016年9月21日 下午1:46:46
 */
public class LiftOff implements Runnable {

    public LiftOff(){
        taskCount++;// 计数自增
    }

    private int        countDown = 3;        // 倒计时数字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    private boolean isOver = false;

    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("线程编号" + id + "--倒计时" + countDown);
            countDown--;
            if(countDown < 0){
                isOver = true;
            }
            Thread.yield();
        }
    }

    public boolean isOver() {
        return isOver;
    }
    
}

  咱们添加了isOver变量,在倒计时结束时将isOver置为true,主函数中咱们不断地判断isOver的状态,就能够判断LiftOff线程是否执行完毕:ide

public class Launch {

    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        Thread t = new Thread(liftOff);
        t.start();
        while (true) {
            if (liftOff.isOver()) {
                System.out.println("发射!");
                break;
            }
        }
    }
}

  执行main(),输出:函数

线程编号0--倒计时3
线程编号0--倒计时2
线程编号0--倒计时1
线程编号0--倒计时0
发射!

  这个解决方案是可行的,它会以正确的顺序给出正确的结果,可是不停地查询不只浪费性能,而且有可能会因主线程太忙于检查工做的完成状况,以致于没有给具体的工做线程留出时间,更好的方式是使用回调(callback),在线程完成时反过来调用其建立者,告诉其工做已结束:性能

public class LiftOff implements Runnable {
    
    private Launch launch;

    public LiftOff(Launch launch){
        taskCount++;// 计数自增
        this.launch = launch;
    }

    private int        countDown = 3;        // 倒计时数字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    @Override
    public void run() {

        while (countDown >= 0) {
            System.out.println("线程编号" + id + "--倒计时" + countDown);
            countDown--;
            if(countDown < 0){
                launch.callBack();
            }
            Thread.yield();
        }
    }
}

  主线程代码:this

public class Launch {
    
    public void callBack(){
        System.out.println("发射!");
    }
    
    public static void main(String[] args) {
        
        Launch launch = new Launch();
        LiftOff liftOff = new LiftOff(launch);
        
        Thread t = new Thread(liftOff);
        t.start(); 
    }
}

  运行结果:spa

线程编号0--倒计时3
线程编号0--倒计时2
线程编号0--倒计时1
线程编号0--倒计时0
发射!

  相比于轮询机制,回调机制的第一个优势是不会浪费那么多的CPU性能,但更重要的优势是回调更灵活,能够处理涉及更多线程,对象和类的更复杂的状况。线程

  例如,若是有多个对象对线程的计算结果感兴趣,那么线程能够保存一个要回调的对象列表,这些对计算结果感兴趣的对象能够经过调用方法把本身添加到这个对象列表中完成注册。当线程处理完毕时,线程将回调这些对计算结果感兴趣的对象。咱们能够定义一个新的接口,全部这些类都要实现这个新接口,这个新接口将声明回调方法。这种机制有一个更通常的名字:观察者(Observer)设计模式。

Callable

  java5引入了多线程编程的一个新方法,能够更容易地处理回调。任务能够实现Callable接口而不是Runnable接口,经过Executor提交任务而且会获得一个Future,以后能够向Future请求获得任务结果:

public class LiftOff implements Callable<String> {
    
    public LiftOff(){
        taskCount++;// 计数自增
    }

    private int        countDown = 3;        // 倒计时数字

    private static int taskCount = 0;

    private int        id        = taskCount;
    
    @Override
    public String call() throws Exception {
        while (countDown >= 0) {
            System.out.println("线程编号" + id + "--倒计时" + countDown);
            countDown--;
        }
        return "线程编号" + id + "--结束";
    }
}

  主函数:

public class Launch {
    
    public static void main(String[] args) {
        
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<String> future = executor.submit(new LiftOff());
        try {
            String s = future.get();
            System.out.println(s);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("发射!");
    }
}

  运行结果:

线程编号0--倒计时3
线程编号0--倒计时2
线程编号0--倒计时1
线程编号0--倒计时0
线程编号0--结束
发射!

  容易使用Executor提交多个任务:

public class Launch {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newCachedThreadPool();
        List<Future<String>> results = new ArrayList<>();
        
        //多线程执行三个任务
        for (int i = 0; i < 3; i++) {
            Future<String> future = executor.submit(new LiftOff());
            results.add(future);
        }
        
        //得到线程处理结果
        for (Future<String> result : results) {
            try {
                String s = result.get();
                System.out.println(s);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        
        //继续主线程流程
        System.out.println("发射!");
    }
}

  结果:

线程编号0--倒计时3
线程编号0--倒计时2
线程编号0--倒计时1
线程编号0--倒计时0
线程编号2--倒计时3
线程编号2--倒计时2
线程编号1--倒计时3
线程编号1--倒计时2
线程编号1--倒计时1
线程编号1--倒计时0
线程编号2--倒计时1
线程编号2--倒计时0
线程编号0--结束
线程编号1--结束
线程编号2--结束
发射!

  能够看到,Future的get()方法,若是线程的结果已经准备就绪,会当即获得这个结果,若是尚未准备好,轮询线程会阻塞,直到结果准备就绪。

好处

  使用Callable,咱们能够建立不少不一样的线程,而后按照须要的顺序获得咱们想要的答案。另外若是有一个很耗时的计算问题,咱们也能够把计算量分到多个线程中去处理,最后汇总每一个线程的处理结果,从而节省时间。

相关文章
相关标签/搜索