资讯专栏INFORMATION COLUMN

Java 多线程(1):获得线程的返回结果

VincentFF / 3085人阅读

摘要:使用获得返回结果新建个线程,每个线程分别负责累加慢速累加器线程等待线程执行完毕累加的结果运行结束,结果为运行结果第二种方法使用和。

Java 对多线程编程提供了内置的支持并提供了良好的 API,通过使用 ThreadRunnable 两个基础类,我们可以很方便的创建一个线程:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("线程启动");
        // 耗时操作
        System.out.println("线程结束");
    }
};

Thread thread = new Thread(runnable); // 创建线程,runnable 作为线程要执行的任务(载体)
thread.start(); // 启动线程
thread.join(); // 等待线程执行完毕

{ 题外话开始:

通过 Thread 的类声明:

我们可以知道 Thread 自己也实现了 Runnable 接口,Threadrun 方法的实现如下(Thread 启动之后运行的就是 Thread 中的 run 方法):

target 即构造 Thread 时可传入的 Runnable 对象,不传入即为 null —— 所以继承 Thread 重写其 run 方法也是一种创建线程的方式)

题外话结束 }

Runnable.java 的代码:

Runnablerun 方法是不带返回值的,那如果我们需要一个耗时任务在执行完之后给予返回值,应该怎么做呢?

第一种方法:在 Runnable 的实现类中设置一个变量 V,在 run 方法中将其改变为我们期待的结果,然后通过一个 getV() 方法将这个变量返回。

import java.util.*;

public class RunnableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Runnable 获得返回结果:");

        List workers = new ArrayList<>(10);
        List tasks = new ArrayList<>(10);

        // 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);
            Thread worker = new Thread(task, "慢速累加器线程" + i);

            tasks.add(task);
            workers.add(worker);

            worker.start();
        }

        int total = 0;
        for (int i = 0, s = workers.size(); i < s; i++) {
            workers.get(i).join();  // 等待线程执行完毕
            total += tasks.get(i).getResult();
        }

        System.out.println("
累加的结果: " + total);
    }

    static final class AccumRunnable implements Runnable {

        private final int begin;
        private final int end;

        private int result;

        public AccumRunnable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public void run() {
            result = 0;
            try {
                for (int i = begin; i <= end; i++) {
                    result += i;
                    Thread.sleep(100);
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace(System.err);
            }
            System.out.printf("(%s) - 运行结束,结果为 %d
",
                    Thread.currentThread().getName(), result);
        }

        public int getResult() {
            return result;
        }
    }

}

运行结果:

第二种方法:使用 CallableFutureTask
Callable 是 JDK1.5 时添加的类,为的就是解决 Runnable 的痛点(没有返回值不能抛出异常)。

Callable.java 的代码:

可以看到参数化类型 V 就是返回的值的类型。

但是查看 Thread 的构造方法,我们发现 Thread 只提供了将 Runnable 作为参数的构造方法,并没有使用 Callable 的构造方法 —— 所以引出 FutureTask

FutureTask 也是 JDK1.5 时添加的类,查看它的类声明:

可以看到它实现了 RunnableFuture 这个接口,我们再看看 RunnableFuture

可以看到 RunnableFuture 接口继承了 Runnable 接口,那么 RunnableFuture 接口的实现类 FutureTask 必然会去实现 Runnable 接口 —— 所以 FutureTask 可以用来当 Runnable 使用。查看 FutureTask 的构造方法,发现 FutureTask 有两个构造方法:

第一个构造方法表明我们可以通过一个 Callable 去构造一个 FutureTask —— 而 FutureTask 实现了 Runnable 接口,从而可以将该任务传递给 Thread 去运行;

第二个构造方法是通过一个 Runnable 和一个指定的 result 去构造 FutureTask

我们再来看看 FutureTask 通过 RunnableFuture 实现的第二个接口:Future
(事实上,RunnableFuture 是在 JDK1.6 时添加的类,我猜测在 JDK1.5 时 FutureTask 应该是直接实现的 RunnableFuture,而不是通过 RunnableFuture

Future.java 的源码:

Future 包含的方法有 5 个,本文只关注它两个 get 相关的方法。通过 Java 的 API 文档,我们可以知道 get 方法是用来返回和 Future 关联的任务的结果(即构造 FutureTask 时使用的 Callable 的结果)。带参数的 get 方法指定一个超时时间,在超时时间内该方法会阻塞当前线程,直到获得结果之后停止阻塞继续运行 —— 如果在给定的超时时间内没有获得结果,那么便抛出 TimeoutException 异常;不带参数的 get 可以理解为超时时间无限大,即一直等待直到获得结果。

Future 每个方法的详细用法可以参考 Java 多线程(3))

通过以上我们可以知道,CallableFutureFutureTask 这三个类是相辅相成的。以上提到关键类的类关系如下:

现在我们看看 FutureTask 中实现 Runnablerun 方法是怎样的(我们直接看关键代码):

代码的意思很明确,调用构造 FutureTask 时传入的 Callablecall 方法,如果正常执行完毕,那么通过 set(result) 设置结果,通过 get() 方法得到的即为这个结果 —— 思路和前面第一种方法是一致的(当然 FutureTask 是更强大和更通用的类);否则即为抛出异常的情况。

使用 FutureTask 的过程如下:
(1)通过一个 Callable 任务或者一个 Runnable(一开始就指定 result)任务构造 FutureTask
(2)将 FutureTask 交给 Thread 去运行;
(3)使用 FutureTaskget 方法(或者 Threadjoin 方法)阻塞当前线程直到获得任务的结果。

现在我们使用 Callable 改写程序:

import java.util.*;
import java.util.concurrent.*;

public class CallableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Callable 获得返回结果:");
        
        List> futureTasks = new ArrayList<>(10);
        // 新建 10 个线程,每个线程分别负责累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);
            FutureTask futureTask = new FutureTask<>(task);
            futureTasks.add(futureTask);
            
            Thread worker = new Thread(futureTask, "慢速累加器线程" + i);
            worker.start();
        }

        int total = 0;
        for (FutureTask futureTask : futureTasks) {
            total += futureTask.get(); // get() 方法会阻塞直到获得结果
        }

        System.out.println("累加的结果: " + total);
    }

    static final class AccumCallable implements Callable {

        private final int begin;
        private final int end;

        public AccumCallable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public Integer call() throws Exception {
            int result = 0;
            for (int i = begin; i <= end; i++) {
                result += i;
                Thread.sleep(100);
            }
            System.out.printf("(%s) - 运行结束,结果为 %d
",
                    Thread.currentThread().getName(), result);

            return result;
        }
     
    }
    
}

运行结果:

可以看到使用 Callable + FutureTask 的程序代码要比 Runnable 的代码更简洁和方便 —— 当需要线程执行完成返回结果时(或者任务需要抛出异常),Callable 是优先于 Runnable 的选择。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66355.html

相关文章

  • Java线程学习(六)Lock锁使用

    摘要:返回与此锁相关联的给定条件等待的线程数的估计。查询是否有线程正在等待获取此锁。为公平锁,为非公平锁线程运行了获得锁定运行结果公平锁的运行结果是有序的。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 ...

    Caicloud 评论0 收藏0
  • Java线程(2):使用线程池 ThreadPoolExecutor

    摘要:本文只介绍中线程池的基本使用,不会过多的涉及到线程池的原理。可缓存线程的线程池创建一个可缓存线程的线程池。首先是从接口继承到的方法使用该方法即将一个任务交给线程池去执行。方法方法的作用是向线程池发送关闭的指令。 首先,我们为什么需要线程池?让我们先来了解下什么是 对象池 技术。某些对象(比如线程,数据库连接等),它们创建的代价是非常大的 —— 相比于一般对象,它们创建消耗的时间和内存都...

    darry 评论0 收藏0
  • Java 线程(5):Fork/Join 型线程池与 Work-Stealing 算法

    摘要:时,标准类库添加了,作为对型线程池的实现。类图用来专门定义型任务完成将大任务分割为小任务以及合并结果的工作。 JDK 1.7 时,标准类库添加了 ForkJoinPool,作为对 Fork/Join 型线程池的实现。Fork 在英文中有 分叉 的意思,而 Join 有 合并 的意思。ForkJoinPool 的功能也是如此:Fork 将大任务分叉为多个小任务,然后让小任务执行,Join...

    IamDLY 评论0 收藏0
  • bat等大公司常考java线程面试题

    摘要:典型地,和被用在等待另一个线程产生的结果的情形测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用使其恢复。使当前线程放弃当前已经分得的时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得时间。 1、说说进程,线程,协程之间的区别 简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程中拥有独立的内存单元...

    Charlie_Jade 评论0 收藏0
  • Java线程Java线程执行框架

    摘要:线程执行框架启动线程将要多线程执行的任务封装为一个对象将其传给一个执行框架对象从线程池中选择线程执行工作任务。 为什么需要执行框架呢?使用一般的new方法来创建线程有什么问题呢?一般的new线程的方式一般要给出一个实现了Runnable接口的执行类,在其中重写run()方法,然后再在将这个执行类的对象传给线程以完成初始化,这个过程中线程的定义和执行过程其实是杂糅在一起了,而且每次new...

    silencezwm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<