如何从线程返回信息——轮询、回调、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 ...
随机推荐
- An internal error occurred during: "Launching PmallSearch on Tomcat 7.x". java.lang.NullPointerException
如果出现了上述的错误按照如下的3个步骤解决:1.首先关闭MyEclipse工作空间.2.然后删除工作空间下的“/.metadata/.plugins/org.eclipse.core.runtime/ ...
- ASP.NET Core 数据保护(Data Protection)【中】
前言 上篇主要是对 ASP.NET Core 的 Data Protection 做了一个简单的介绍,本篇主要是介绍一下API及使用方法. API 接口 ASP.NET Core Data Prote ...
- iOS开发系列--并行开发其实很容易
--多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的, ...
- 谁能在同一文件序列化多个对象并随机读写(反序列化)?BinaryFormatter、SoapFormatter、XmlSerializer还是BinaryReader
谁能在同一文件序列化多个对象并随机读写(反序列化)?BinaryFormatter.SoapFormatter.XmlSerializer还是BinaryReader 随机反序列化器 +BIT祝威+悄 ...
- 七天学会ASP.NET MVC (六)——线程问题、异常处理、自定义URL
本节又带了一些常用的,却很难理解的问题,本节从文件上传功能的实现引出了线程使用,介绍了线程饥饿的解决方法,异常处理方法,了解RouteTable自定义路径 . 系列文章 七天学会ASP.NET MVC ...
- Session中放错误提示JSP上获取
servlet中: message= "图片上传失败!"; request.setAttribute("message",message); JSP中运用一 ...
- chrome使用技巧(看了定不让你失望)
写在前面 之前有看过刘哇勇写的Chrome 控制台不完全指南,让我觉得瞬间对chrome的了解实在太浅了.对此特意了解了一番(也就是在他的博文上进行了一些总结和了解一些其它chrome使用方面的诀窍) ...
- Birt报表存储过程多选参数的设置
Birt对存储过程的操作是很简单的一行语句,只需要在Data Set中写上类似这样 {call CAMPAIGN_REAL_TIME_MONITOR(?,?)} 如下图 本报表是存在两个参数,一个允许 ...
- python守护线程
如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出.如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性.即在线程开 ...
- 实战MEF(5):导出元数据
如何理解元数 我们可以把元数据理解为随类型一起导出的附加信息.有时候我们会考虑,把元数据随类型一并导出,增加一些说明,使得我们在导入的时候,可以多一些筛选条件. 默认的类型导出带有元数据吗 上面的内容 ...