Java中Future的使用场景和解析

Java中Future的使用场景和解析

我们通常都是开启一个新的子线程去执行比较耗时的代码,这使用起来非常简单,只需要将耗时的代码封装在Runnable中的run()方法里面,然后调用thread.start()就行。但是我相信很多人有时候都有这样的需求,就是获取子线程运行的结果,比如客户端远程调用服务(耗时服务),我们有需要得到该调用服务返回的结果,这该怎么办呢?很显然子线程运行的run()方法是没有返回值。这个时候Future的作用就发挥出来了。

Future如何使用能够获取子线程运行的结果呢?在这里顺便提一下Callable接口,Callable产生结果,Future获取结果。如何使用他们两个来获取子线程的运行结果呢?我们先来看个简单的例子。

package test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableFutureTest {

	public static void main(String[] args) {
		long startTime = System.currentTimeMillis();
		Callable<Integer> calculateCallable = new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				// TODO Auto-generated method stub
				Thread.sleep(2000);//模拟耗时时间
				int result = 1+2;
				return result;
			}
		};
		FutureTask<Integer> calculateFutureTask = new FutureTask<>(calculateCallable);
		Thread t1 = new Thread(calculateFutureTask);
		t1.start();
		//现在加入Thread运行的是一个模拟远程调用耗时的服务,并且依赖他的计算结果(比如网络计算器)
		try {
			//模拟耗时任务,主线程做自己的事情,体现多线程的优势
			Thread.sleep(3000);
			int a = 3+5;
			Integer result = calculateFutureTask.get();
			System.out.println("result = "+(a+result));//模拟主线程依赖子线程的运行结果
			long endTime = System.currentTimeMillis();
			System.out.println("time = "+(endTime-startTime)+"ms");
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 运行结果如下:

Java中Future的使用场景和解析

 

 

从上面可以看到上面耗时大概是3s,其实主要就是主线程sleep(3000)所耗费的时间,如果不使用Future,并且依赖线程的结果,我们可能需要的时间可能是需要5s(子线程2s+主线程3s)。接下来我们从源码的角度理解上述代码工作的原理。

1,先看看类的FureTask类的关系图

 

Java中Future的使用场景和解析

 

FutureTask实现了RunnableTask接口,RunnableTask继承了Runnable和Future接口(接口是支持多继承的)

 

2,t1.start()执行的是FutureTask类的run()方法,看下FutureTask.run()方法源码

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;//构造方法传入的calculateCallable
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();//最终执行的是callablede.call()方法,有返回值的
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);//保存抛出的异常
                }
                if (ran)
                    set(result);//保存执行的结果
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

 

3,执行的Callable.call(),即执行了calculateCallable .call(),得到call()返回的结果

接下来看看,如何将执行的结果保存起来,然后方便Future获取到,那就是调用set(result)方法

4,看看set(result)方法

protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;//将result赋值给outcome
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

/**
     * Removes and signals all waiting threads, invokes done(), and
     * nulls out callable.
     */
//这个方法,就是当执行完成完成的时候,唤醒get()方法挂起的线程,从而使得get()方法在阻塞
//的for循环中能够正确的获取执行完的结果
    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);//唤醒线程
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

 

最终FutureTask中的outcome变量为执行的结果

 

5,接下来看FutureTask.get()方法如何获取执行完的结果

//get方法表示如果执行过程完成,就获取执行的结果,否则就将当前线程挂起
public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);//如果状态没完成
        return report(s);//返回执行完的结果
    }

//这里是执行的状态
private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {//堵塞的方法
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();//将线程挂起,让出cpu资源
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }


private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;//返回结果
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

通过上面的几个方法看出,FutureTask.get()是一个阻塞的方法,直到运行完成之后,返回线程执行的结果。

 上述中出现很多状态的常量(NEW、COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTING 、INTERRUPTED)这些状态表示执行的过程,一般执行的过程转变如下:

1)一个FutureTask新建出来,state就是NEW状态;COMPETING和INTERRUPTING用的进行时,表示瞬时状态,存在时间极短;NORMAL代表顺利完成;EXCEPTIONAL代表执行过程出现异常;CANCELED代表执行过程被取消;INTERRUPTED被中断

2)执行过程顺利完成:NEW -> COMPLETING -> NORMAL

3)执行过程出现异常:NEW -> COMPLETING -> EXCEPTIONAL

4)执行过程被取消:NEW -> CANCELLED

5)执行过程中,线程中断:NEW -> INTERRUPTING -> INTERRUPTED

另外FutureTask中还有其他的方法,如cancel()-取消,isDone()-判断是否执行完,isCancelled()-判断执行是否取消等,感兴趣的可以自己去看相应的源码

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111247.html原文链接:https://javaforall.net

(0)
上一篇 2021年8月2日 下午6:00
下一篇 2021年8月2日 下午7:00


相关推荐

  • 最全Pycharm教程(17)——Pycharm编辑器功能之自动导入模块

    最全Pycharm教程(17)——Pycharm编辑器功能之自动导入模块  1、导入模块  我们在编程过程中经常会不经意的使用到一些尚未导入的类和模块,在这种情况下Pycharm会帮助我们定位模块文件位置并将其添加到导入列表中,这也就是所谓的自动导入模块功能。  为了研究这个功能,我们借用之前已经编写好的Solver类,输入以下代码:  在输入math.sqrt(d)的时候,Pycharm会弹出一个菜单来提示你导入缺失的模块:  按下Alt+Enter,采取快捷菜单中…

    2022年8月28日
    6
  • java 生成二维码

    java 生成二维码最近在做一个自提的需求 当用户下单后给该笔订单生成一个二维码 当用户去实体店自提的时候 实体店扫用户提供的二维码 这样该笔订单就算完成了 1 相关的依赖 dependency groupId com google zxing groupId artifactId javase artifactId version 3 3 0 amp l version dependency

    2026年3月17日
    2
  • 智谱上线并开源GLM-4.7

    智谱上线并开源GLM-4.7

    2026年3月12日
    2
  • 2021年Java后端开发学习路线(建议收藏!)

    2021年Java后端开发学习路线(建议收藏!)2021 的 Java 后端开发的学习路线欢迎使用 Markdown 编辑器大家好 这是你第一次写 CSDN 的博客 下面我为大家来介绍一下 2021 年学习 Java 的路线 让初学者少走弯路跟着路线走 一定可以找到心仪的 offer 还请大家不吝赐教 学习路线大致分为十部分和一个扩展 第一部分 Java 基础 Java 基础部分 初学者在初学 Java 时一定不要好高骛远 认认真真打好基础才行 虽然基础部分有点枯燥无味但是选择了就得坚持下去 罗马并非一日建成 如今基础课程娱乐混杂 选对精品很重要 B 站上很多视频 大家可以去试听

    2026年3月20日
    3
  • Q1营收利润大增,Take-Two如何掘金“次世代”?[通俗易懂]

    Q1营收利润大增,Take-Two如何掘金“次世代”?[通俗易懂]8月3日美股盘后,拥有GTA和2K等知名系列游戏的Take-Two(NASDAQ:TTWO)发布了截至2020年6月30日的2020财年第一季度的业绩报告。财报公布后次日,股价跳空高开,最终股价收于177.52美元,涨幅达5.87%。回顾近期走势,TTWO已连续创下历史新高,可见其一直深受投资者青睐。(图源:雪球)以下为近期核心数据表现:由于全球疫情居家,TTWO受益颇多。本季度无论是营收、净利润,还是各产品的销量均超过市场预期。此次财报有着许多亮点值得深入讨论,而除此之外,也希望随着新品推

    2022年6月7日
    32
  • 2010年软件外包企业排名, 软件外包公司排名2010

    2010年软件外包企业排名, 软件外包公司排名20101. 博朗软件 Bleum(上海)2. 中软国际(北京)3. 东软集团 Neusoft(沈阳)4. 博彦科技 BeyondSoft(北京)5. 海辉软件 HiSoft(大连)6. 文思 VanceInfo(北京)7. 浙大网新 Insigma (杭州)8. 奥博杰天 Objectiva(北京)9. 浪潮 Inspur(济南)…

    2022年5月5日
    69

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号