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()-判断执行是否取消等,感兴趣的可以自己去看相应的源码

 

 

 

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • latex中双引号和单引号的正确应用[通俗易懂]

    latex中双引号和单引号的正确应用[通俗易懂]用输入法中的中文模式键入的双引号和单引号并不是LaTeX的正确输入方式。正确的输入方法如下:使用键盘的Tab键上侧的连续键入两个“然后输入键盘的双引号’’。单引号输入类似。示例如下:演示代码:\documentclass{article}\setlength\textwidth{150pt}\begin{document}\largePumasare“large,cat-likeanimals’’whichare`foundinAmerica’.Whenr

  • 香农编码简单例题_单链表逆序输出c语言

    香农编码简单例题_单链表逆序输出c语言常量介绍:si:信源符号p(si):该项概率pi:前i-1项累加概率p(s1)+p(s2)+…+p(s(i-1))li:码长,计算公式[-log(p(si))]+1;(下取整)求解步骤:1、将信源符号按从大到小的顺序排列2、求码长li3、求累加概率pi4、将累加概率pi转换为二进制小数,并根据码长li取小数点后li为作为码字介绍:本程序是将原始数据存放到记事本Data.txt中,记事本应…

  • $.ajax()方法详解学习

    在工作总是会有很多地方用到异步请求,有时候用快捷方法 get/post 或者getJson不能满足自己的需求,所以必须使用底层的ajax来实现异步请求,每次写完下次在用到的时候就记不清楚了,就在这里记录一下,方便自己以后使用和其他人学习! 主要是参考整理JQuery的文档和一些好博客内容!记录一下平常工作最常用的!

  • Java stream groupby_javadoop

    Java stream groupby_javadoopgroupingBy()是StreamAPI中最强大的收集器Collector之一,提供与SQL的GROUPBY子句类似的功能。使用形式如下: .collect(groupingBy(…)); 需要指定一个属性才能使用,通过该属性执行分组。我们通过提供功能接口的实现来实现这一点-通常通过传递lambda表达式。例如,如果我…

  • APACHE rewriterule 规则「建议收藏」

    APACHE rewriterule 规则「建议收藏」Apache/RewriteRule最最好去读官方说明。http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html ——buaagengRewriteRule指令是重写引擎的根本。此指令可以多次使用。每个指令定义一个简单的重写规则。这些规则的定义顺序尤为重要——在运行时,规则是按这个顺序逐一生效的。

  • 数学建模中的选址问题_数学建模停车场规划问题

    数学建模中的选址问题_数学建模停车场规划问题选址问题:是指在规划区域里选择一个或多个设施的位置,使得目标最优。四个要素:设施、规划区域、位置(距离)、目标设施:按照设施的空间维度划分,可以将选址问题分为:1.立体选址问题:设施的高度不能被忽略,如集装箱装箱问题。2.平面选址问题:设施的长、宽不能被忽略,如货运站的仓位布局问题。3.线选址问题:设施的宽度不能被忽略,如在仓库两边的传送带布局问题。4.点选址问题:设施可以…

发表回复

您的电子邮箱地址不会被公开。

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