【转】线程返回值的方式介绍

标签: 线程 | 发表时间:2015-07-23 19:10 | 作者:Everyday都不同
出处:http://www.iteye.com

原文地址: http://smalltalllong.iteye.com/blog/1156407

在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写。或者干脆绕过这道坎,走别的路了。
 
现在Java终于有可返回值的任务(也可以叫做线程)了。
 
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。
 
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
 
下面是个很简单的例子:
Java代码  
import java.util.concurrent.*;  
  
/** 
* Java线程:有返回值的线程 
* 
* @author Administrator  
*/  
public class Test {  
        public static void main(String[] args) throws ExecutionException, InterruptedException {  
                //创建一个线程池  
                ExecutorService pool = Executors.newFixedThreadPool(2);  
                //创建两个有返回值的任务  
                Callable c1 = new MyCallable("A");  
                Callable c2 = new MyCallable("B");  
                //执行任务并获取Future对象  
                Future f1 = pool.submit(c1);  
                Future f2 = pool.submit(c2);  
                //从Future对象上获取任务的返回值,并输出到控制台  
                System.out.println(">>>"+f1.get().toString());  
                System.out.println(">>>"+f2.get().toString());  
                //关闭线程池  
                pool.shutdown();  
        }  
}  
  
class MyCallable implements Callable{  
        private String oid;  
  
        MyCallable(String oid) {  
                this.oid = oid;  
        }  
  
        @Override  
        public Object call() throws Exception {  
                return oid+"任务返回的内容";  
        }  
}  
 输出结果:
>>>A任务返回的内容 
>>>B任务返回的内容 

Process finished with exit code 0

非常的简单,要深入了解还需要看Callable和Future接口的API啊。

第二种方法:
从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。但类成员在返回数据和传递数据时有一些区别,下面让我们来看看它们区别在哪。
  一、通过类变量和方法返回数据
  使用这种方法返回数据需要在调用start方法后才能通过类变量或方法得到数据。让我们先来看看会得到什么结果。
Java代码  
package mythread;  
  
public class MyThread extends Thread  
{  
    private String value1;  
    private String value2;  
  
    public void run()  
    {  
        value1 = "通过成员变量返回数据";  
        value2 = "通过成员方法返回数据";  
    }  
    public static void main(String[] args) throws Exception  
    {  
        MyThread thread = new MyThread();  
        thread.start();  
        System.out.println("value1:" + thread.value1);  
        System.out.println("value2:" + thread.value2);  
    }  
}  
 运行上面的代码有可能输出如下的结果:
  value1:null
  value2:null
  从上 面的运行结果看很不正常。在run方法中已经对value1和value2赋了值,而返回的却是null。发生这种情况的原因是调用start方法后就立 刻输出了value1和value2的值,而这里run方法还没有执行到为value1和value2赋值的语句。要避免这种情况的发生,就需要等run 方法执行完后才执行输出value1和value2的代码。因此,我们可以想到使用sleep方法将主线程进行延迟,如可以在 thread.start()后加一行如下的语句:sleep(1000);
  这样做可以使主线程延迟1秒后再往下执行,但这样做有一个问题,就是我们怎么知道要延迟多长时间。在这 个例子的run方法中只有两条赋值语句,而且只创建了一个线程,因此,延迟1秒已经足够,但如果run方法中的语句很复杂,这个时间就很难预测,因此,这 种方法并不稳定。
  我们的目的就是得到value1和value2的值,因此,只要判断value1和value2是否为null。如果它们都不为null时,就可以输出这两个值了。我们可以使用如下的代码来达到这个目的:
  while (thread.value1 == null || thread.value2 == null);
   使用上面的语句可以很稳定地避免这种情况发生,但这种方法太耗费系统资源。大家可以设想,如果run方法中的代码很复杂,value1和value2需 要很长时间才能被赋值,这样while循环就必须一直执行下去,直到value1和value2都不为空为止。因此,我们可以对上面的语句做如下的改进:
  while (thread.value1 == null || thread.value2 == null)
   sleep(100);
  在while循环中第判断一次value1和value2的值后休眠100毫秒,然后再判断这两个值。这样所占用的系统资源会小一些。
   上面的方法虽然可以很好地解决,但Java的线程模型为我们提供了更好的解决方案,这就是join方法。在前面已经讨论过,join的功能就是使用线程 从异步执行变成同步执行。当线程变成同步执行后,就和从普通的方法中得到返回数据没有什么区别了。因此,可以使用如下的代码更有效地解决这个问题:
Java代码  
...  
thread.start();  
thread.join();  
...  
 在thread.join()执行完后,线程thread的run方法已经退出了,也就是说线程thread已经结束了。因此,在thread.join()后面可以放心大胆地使用MyThread类的任何资源来得到返回数据。 

第三种:
通过回调函数返回数据
  下面例子中通过Work类的process方法向线程中传递了计算结果,但同时,也通过process方法从线程中得到了三个随机数。因此,这种方法既可以向线程中传递数据,也可以从线程中获得数据。
Java代码  
package mythread;  
  
class Data  
{  
    public int value = 0;  
}  
class Work  
{  
    public void process(Data data, Integer numbers)  
    {  
        for (int n : numbers)  
        {  
            data.value += n;  
        }  
    }  
}  
public class MyThread3 extends Thread  
{  
    private Work work;  
  
    public MyThread3(Work work)  
    {  
        this.work = work;  
    }  
    public void run()  
    {  
        java.util.Random random = new java.util.Random();  
        Data data = new Data();  
        int n1 = random.nextInt(1000);  
        int n2 = random.nextInt(2000);  
        int n3 = random.nextInt(3000);  
        work.process(data, n1, n2, n3);   // 使用回调函数  
        System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"  
                + String.valueOf(n3) + "=" + data.value);  
    }  
    public static void main(String[] args)  
    {  
        Thread thread = new MyThread3(new Work());  
        thread.start();  
    }  
}  
 在上面代码 中的 process方法被称为回调函数。从本质上说,回调函数就是事件函数。在 Windows API中常使用回调函数和调用 API的程序之间进行数据交互。因此,调用回调函数的过程就是最原始的引发事件的过程。在这个例子中调用了 process方法来获得数据也就相当于在 run方法中引发了一个事件

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [线程] 推荐:

MySQL Replication 线程

- - CSDN博客推荐文章
Replication 线程. Mysql 的Replication 是一个异步的复制过程,从一个Mysql instace(我们称之为Master)复制到另一个Mysql instance(我们称之Slave). 在Master 与Slave 之间的实现整个复制过程主. 要由三个线程来完成,其中两个线程(Sql 线程和IO 线程)在Slave 端,另外一个线程(IO 线程)在Master 端.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

Java 线程池

- - 编程语言 - ITeye博客
在项目中,系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互. 在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存周期很短的线程时,更应该考虑使用线程池. 使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不超过此数.

Java线程(四):线程中断、线程让步、线程睡眠、线程合并

- - 编程语言 - ITeye博客
 最近在Review线程专栏,修改了诸多之前描述不够严谨的地方,凡是带有Review标记的文章都是修改过了. 本篇文章是插进来的,因为原来没有写,现在来看传统线程描述的不太完整,所以就补上了. 理解了线程同步和线程通信之后,再来看本文的知识点就会简单的多了,本文是做为传统线程知识点的一个补充. 有人会问:JDK5之后有了更完善的处理多线程问题的类(并发包),我们还需要去了解传统线程吗.

Java Thread多线程

- - CSDN博客推荐文章
Java Thread多线程. Java 多线程例子1 小例子. super("zhuyong");//设置线程的名字,默认为“TestThread”. Java 多线程例子2 前台线程(用户线程) 后台线程(守护线程 ). 1,setDaemon(true)后就是后台线程(守护线程 ),反之就是前台线程(用户线程).

Android线程大坑

- - 移动开发 - ITeye博客
     android界面的更新实在主线程进行的,通常把主线程也叫UI线程,UI线程里进行事件的分发和交互. 在UI线程中进行耗时操作,比如网络请求,IO操作等会阻塞UI线程,界面会卡住,并且超过大概5秒钟程序会ANR(Application Not Responding),也就是死掉. 其实这种GUI单线程的思想在我上一篇博客(http://zyqwst.iteye.com/blog/2262011)都有阐述,道理一模一样,只是android实现的方式上略有不同,所以我建议把上一篇Swing线程的博客能够阅读一遍,Android线程的问题豁然开朗,始终晋级GUI开发的原则:在UI线程中进行界面的更新操作,在单独线程中进行耗时操作.

浅谈 iOS 线程

- - SegmentFault 最新的文章
通常主线程和其他线程的使用场景. Tips: 解压、打开 Zip 包,读写较大文件的操作也不宜放在主线程里. 一般异步网络请求中会有一个 completionBlock ,这个 completionBlock 是在主线程中被调用的. 所以,可能消耗大量时间的代码(例如上面提到的处理 Zip 包的方法)也不宜放在这些 block 中.

Java线程之FutureTask

- - zzm
FutureTask是Future和Callable的结合体. 然后通过Future来取得计算结果. 但是,若开启了多个任务,我们无从知晓哪个任务最先结束,因此,若要实现“当某任务结束时,立刻做一些事情,例如记录日志”这一功能,就需要写一些额外的代码. FutureTask正是为此而存在,他有一个回调函数protected void done(),当任务结束时,该回调函数会被触发.

Java多线程之synchronized

- - CSDN博客推荐文章
这里通过三个测试类阐述了synchronized应用的不同场景. 首先是最基本的synchronized Method的使用.  * @see 概述:Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor) .  * @see 说明:当synchronized关键字修饰一个方法时,则该方法为同步方法 .

[转]GDB调试多线程

- - 小彰
GDB 多线程调试基本命令 实现简介 以及一个问题的解决. 一直对GDB多线程调试接触不多,最近因为工作有了一些接触,简单作点记录吧. 先介绍一下GDB多线程调试的基本命令. 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID. 切换当前调试的线程为指定ID的线程.