如何从线程返回信息——轮询、回调、Callable
考虑有这样一个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的更多相关文章
- Java多线程和并发(四),线程返回值获取方式和Callable接口
目录 1.主线程等待法 2.使用Thread类的join()阻塞当前线程,等待子线程执行完毕 3.通过Callable接口实现:通过FutureTask Or线程池获取 四.线程返回值获取方式和Cal ...
- 基于springboot实现轮询线程自动执行任务
本文使用: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时 ...
- c#调用JAVA的Webservice处理XML数据及批量轮询的实现方法
前段时间做一个调用外单位WEBSERVICE的项目,项目完成的功能其实很简单,就是我们单位有很多车友会员,我们想对他们提供车辆违章信息告之服务!我们这边交警部门给我们开放了WS的接口,我们就是想通过这 ...
- 为什么JAVA要提供 wait/notify 机制?是为了避免轮询带来的性能损失
wait/notify 机制是为了避免轮询带来的性能损失. 为了说清道理,我们用“图书馆借书”这个经典例子来作解释. 一本书同时只能借给一个人.现在有一本书,图书馆已经把这本书借了张三. 在简单的s ...
- 【Javascript】解决Ajax轮询造成的线程阻塞问题(过渡方案)
一.背景 开发Web平台时,经常会需要定时向服务器轮询获取数据状态,并且通常不仅只开一个轮询,而是根据业务需要会产生数个轮询.这种情况下,性能低下的Ajax长轮询已经不能满足需求,频繁的访问还会造成线 ...
- java用while循环设计轮询线程的性能问题
java用while循环设计轮询线程的性能问题 轮询线程在开发过程中的应用是比较广泛的,在这我模拟一个场景,有一个队列和轮询线程,主线程往队列中入队消息,轮询线程循环从队列中读取消息并打印消息内容.有 ...
- 用.NET MVC实现长轮询,与jQuery.AJAX即时双向通信
两周前用长轮询做了一个Chat,并移植到了Azure,还写了篇博客http://www.cnblogs.com/indream/p/3187540.html,让大家帮忙测试. 首先感谢300位注册用户 ...
- Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
1. 前言 Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Serve ...
- polling轮询和comet
comet:(原意:彗星) Comet is a web application model in which a long-held(held:保留) HTTP request allows a w ...
随机推荐
- Ajax是如何运行的?
1.我们需要知道什么是Ajax: AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现 ...
- ASP.NET Web API 接口执行时间监控
软件产品常常会出现这样的情况:产品性能因某些无法预料的瓶颈而受到干扰,导致程序的处理效率降低,性能得不到充分的发挥.如何快速有效地找到软件产品的性能瓶颈,则是我们感兴趣的内容之一. 在本文中,我将解释 ...
- 删除Mysql数据表中多余的重复记录的sql语句
数据表 sniper_tb 中存在主键 id,字段url,现需要在url字段上添加 unique,但由于url存在重复记录,导致添加失败. 如何删除表中多余的url重复记录,仅保持一条? 思路一 将 ...
- 剑指Offer面试题:21.从上到下打印二叉树
一.题目:从上到下打印二叉树 题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印.例如输入下图中的二叉树,则依次打印出8.6.10.5.7.9.11. 二叉树节点的定义如下,采用 ...
- .NET实现微博粉丝服务平台接口
[文章摘要]Senparc.Weixin.MP虽然是微信公众号的SDK,但由于易信公众号和新浪微博粉丝服务平台也提供了微信兼容接口,所以也可以使用其快速实现相应的服务,当然微博由于与微信存在差异,如果 ...
- Javascript模拟继承(赠送.net吐槽一段)
首先吐槽一句,今年的就业形势很不乐观啊,特别是搞.net的(相对java),特特别是还没出校门没有正式工作经验的,找个实习很难,前些天接了个面试电话,上来就质疑我“你一个在校大学生怎么可能做了那么多项 ...
- 使用HTML5的History API
HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL.这个功能很有用,例如通过一段JavaScript代码局部加载页面的内容,你希望通过改变当前页面的 ...
- Docker实践:安装wordpress
本文将示例如何使用Docker来安装wordpress.使用三种方法: 1.基于官方的wordpress镜像使用docker run实现: 2.基于官方的wordpress镜像使用fig命令编排工具实 ...
- Spring学习记录(八)---Bean的生命周期
之前说过,在调用下面时,就创建了容器和对象 ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml&quo ...
- HttpUrlConnection发送url请求(后台springmvc)
1.HttpURLConnection发送url请求 public class JavaRequest { private static final String BASE_URL = "h ...