考虑有这样一个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; @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线程是否执行完毕:

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();
}
}
}

  主线程代码:

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();
}
}

  运行结果:

线程编号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,我们可以创建很多不同的线程,然后按照需要的顺序得到我们想要的答案。另外如果有一个很耗时的计算问题,我们也可以把计算量分到多个线程中去处理,最后汇总每个线程的处理结果,从而节省时间。

如何从线程返回信息——轮询、回调、Callable的更多相关文章

  1. Java多线程和并发(四),线程返回值获取方式和Callable接口

    目录 1.主线程等待法 2.使用Thread类的join()阻塞当前线程,等待子线程执行完毕 3.通过Callable接口实现:通过FutureTask Or线程池获取 四.线程返回值获取方式和Cal ...

  2. 基于springboot实现轮询线程自动执行任务

    本文使用: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时 ...

  3. c#调用JAVA的Webservice处理XML数据及批量轮询的实现方法

    前段时间做一个调用外单位WEBSERVICE的项目,项目完成的功能其实很简单,就是我们单位有很多车友会员,我们想对他们提供车辆违章信息告之服务!我们这边交警部门给我们开放了WS的接口,我们就是想通过这 ...

  4. 为什么JAVA要提供 wait/notify 机制?是为了避免轮询带来的性能损失

    wait/notify  机制是为了避免轮询带来的性能损失. 为了说清道理,我们用“图书馆借书”这个经典例子来作解释. 一本书同时只能借给一个人.现在有一本书,图书馆已经把这本书借了张三. 在简单的s ...

  5. 【Javascript】解决Ajax轮询造成的线程阻塞问题(过渡方案)

    一.背景 开发Web平台时,经常会需要定时向服务器轮询获取数据状态,并且通常不仅只开一个轮询,而是根据业务需要会产生数个轮询.这种情况下,性能低下的Ajax长轮询已经不能满足需求,频繁的访问还会造成线 ...

  6. java用while循环设计轮询线程的性能问题

    java用while循环设计轮询线程的性能问题 轮询线程在开发过程中的应用是比较广泛的,在这我模拟一个场景,有一个队列和轮询线程,主线程往队列中入队消息,轮询线程循环从队列中读取消息并打印消息内容.有 ...

  7. 用.NET MVC实现长轮询,与jQuery.AJAX即时双向通信

    两周前用长轮询做了一个Chat,并移植到了Azure,还写了篇博客http://www.cnblogs.com/indream/p/3187540.html,让大家帮忙测试. 首先感谢300位注册用户 ...

  8. Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

    1. 前言 Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Serve ...

  9. polling轮询和comet

    comet:(原意:彗星) Comet is a web application model in which a long-held(held:保留) HTTP request allows a w ...

随机推荐

  1. 用DataGridView导入TXT文件,并导出为XLS文件

    使用 DataGridView 控件,可以显示和编辑来自多种不同类型的数据源的表格数据.也可以导出.txt,.xls等格式的文件.今天我们就先介绍一下用DataGridView把导入txt文件,导出x ...

  2. You are attempting to run the 32-bit installer on a 64-bit version of Window

    您正试图在64位版本的窗口中运行32位安装程序. 系统有32位操作系统和64位操作系统的分别,相同的软件的安装也需要区分操作操作系统的位数. 解决办法:查看自己系统类型,根据类型下载安装相应位数的软件 ...

  3. Tempdb initial size和dbcc shrinkfile

    在使用sql server时您可能遇到过下面的情况,tempdb的数据文件初始大小为3mb, 随着对tempdb的使用,tempdb文件逐渐变得很大(例如30GB),导致了磁盘空间不足. 此时您需要立 ...

  4. 关于MongoDB你需要知道的几件事

    Henrique Lobo Weissmann是一位来自于巴西的软件开发者,他是itexto公司的联合创始人,这是一家咨询公司.近日,Henrique在博客上撰文谈到了关于MongoDB的一些内容,其 ...

  5. Lesson 9 A cold welcome

    Text On Wednesday evening, we went to the Town Hall. It was the last day of the year and a large cro ...

  6. 从viewport发现小米手机参数不一致

    想要在移动web领域有所深造的小伙伴,第一关要过的就是逻辑像素与设备像素之间的关系. 初入移动web,一定要搞懂的几个单位(DPI.PPI.DP.PX 的详细计算方法及算法来源是什么?): dip(d ...

  7. angularjs 指令详解 - template, restrict, replace

    通过指令机制,angularjs 提供了一个强大的扩展系统,我们可以通过自定义指令来扩展自己的指令系统. 怎样定义自己的指令呢? 我们通过 Bootstrap UI来学习吧.这个项目使用 angula ...

  8. ASP.NET MVC 5 Web编程2 -- URL映射(路由原理)

    本章将讲述ASP.NET MVC5 的路由原理,即URL映射机制. 简单点就是解释:为什么MVC在浏览器输入地址就能访问到类(或类中的方法)?这是怎么做到的?我自己可以通过.NET写出一个自己的MVC ...

  9. ORM小练习代码

    DOG类 namespace RupengORM { public class Dog { public Dog() { } /// <summary> /// 显示提供无参构造函数 // ...

  10. C# BS消息推送 SignalR Hubs环境搭建与开发(二)

    1. 前言 本文是根据网上前人的总结得出的. 环境: SignalR2.x,VS2015,Win10 2. 开始开发 1)新建一个MVC项目,叫做SignalRDemo 2)安装SignalR包 In ...