如何从线程返回信息——轮询、回调、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 ...
随机推荐
- 移动端click时间延迟300
解决移动端click延迟事件方法,,引入fastclick.js 然后在script标签里面写上FastClick.attach(document.body); <!DOCTYPE html& ...
- 为首次部署MongoDB做好准备:容量计划和监控
如果你已经完成了自己新的MongoDB应用程序的开发,并且现在正准备将它部署进产品中,那么你和你的运营团队需要讨论一些关键的问题: 最佳部署实践是什么? 为了确保应用程序满足它所必须的服务层次我们需要 ...
- Lesson 6 Percy Buttons
Text I have just moved to a house in Bridge Street. Yesterday a bagger knocked at my door. He asked ...
- JavaScript 事件管理
在设计JavaScript xxsdk的时候考虑到能让调用者参与到工作流程中来,开始用了回调函数.如下: this.foo = function(args,callbackFn) { //do som ...
- Zookeeper API for JAVA实战与应用
package com.zookeeper.watcher; import java.util.List; import java.util.concurrent.CountDownLatch; im ...
- 每天一个linux命令(49):at命令
在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务. 通过'添加任务计划'的一步步引导,则可建立一 ...
- php单条件查询,关键字查询
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Java_IO流_File类配合使用(其中用到了递归)
第一:Java File类的功能非常强大,利用Java基本上可以对文件进行所有的操作.以下对Java File文件操作以及常用方法进行简单介绍 案例1:遍历出指定目录下的文件夹,并输出文件名 stat ...
- IOS下载查看PDF文件(有下载进度)
IOS(object-c) 下载查看 PDF 其实还是蛮容易操作的.在下载前,首先要把 IOS 可以保存文件的目录给过一遍: IOS 文件保存目录 IOS 可以自定义写入的文件目录,是很有限的,只能是 ...
- 学习3ds max插件开发过程中的一些小结
1. 3ds max是以树状结构来管理整个场景的,每个树节点类型为INode 2. Interface类很关键,可以通过其中的GetRootNode.NumberOfChildren和GetChild ...