如何从线程返回信息——轮询、回调、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 ...
随机推荐
- CSS基础篇之了解CSS和它的基本属性
CSS是什么? CSS英文全名是Cascading Style Sheets翻译过来就是层叠样式表,它主是把网页表现与内容分离的一种样式设计语言.这种语言能优化我们编程,把东西简化和优化写法,而且不同 ...
- Ubuntu添加开机自动启动程序方法
1. 开机启动时自动运行程序 Linux加载后, 它将初始化硬件和设备驱动, 然后运行第一个进程init.init根据配置 文件继续引导过程,启动其它进程.通常情况下,修改放置在 / ...
- SQL Server 事件通知(Event notifications)
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 基础知识(Rudimentary Knowledge) 事件通知监控DDL(NotifyQue ...
- JavaScript面向对象之我见
序言 在JavaScript的大世界里讨论面向对象,都要提到两点:1.JavaScript是一门基于原型的面向对象语言 2.模拟类语言的面向对象方式.对于为什么要模拟类语言的面向对象,我个人认为:某些 ...
- iOS开发系列--C语言之指针
概览 指针是C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面: 什么是指针 ...
- 【js技巧】~(按位非)运算符的妙用
今天在reivew部门牙套姐MM的代码的时候,有点小心得,给大家分享.我们常常说到,“学以致用”,但我发现自己却很少能真正做到.<javascript高级程序设计>即大家常说的“红宝书”, ...
- 《Spark快速大数据分析》—— 第七章 在集群上运行Spark
- 修改windows自带的Ctrl+Space输入法切换快捷键
使用场景: 多为我等码农使用一些编辑器时,编辑器的默认代码提示热键为 ctrl+space ,但这个热键被系统的输入法开关占用.如果遇到可以设置快捷键的编辑器还好,要是不能设置的话(比如火狐浏览器的代 ...
- JS原生第七篇 (帅哥)
1.1 复习 offset 自己的 偏移 offsetWidth 得到自己的宽度 offsetHeight 构成 : width + padding + border div ...
- Distribution1:Distribution Reader
在transactional replication中,在publication中执行了一个更新,例如:update table set col=? Where ?,如果table中含有大量的数据行, ...