JUC-Callable、FutureTask接口实现原理(六)

一、Callable、FutureTask简单使用

示例:

public class Test {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		TestCallable testCallable = new TestCallable();
		FutureTask<String> futureTask = new FutureTask<>(testCallable);
		new Thread(futureTask).start();

		String s = futureTask.get();
		System.out.println(s);
	}

	static class TestCallable implements Callable<String>{
		@Override
		public String call() throws Exception {
			System.out.println("call execute");
			return "success";
		}
	}
}

结果:
call execute
success

从简单的例子来看,Callable、FutureTask使用起来非常简单,与runnable接口的使用相差不大。
Callable接口是如何实现线程执行结束后返回值呢?
其实原理并不难,下面我们来一一研究,但正式聊Callable前,我们先说一下runnable和适配器模式,这两个前置知识。

二、Runnable创建线程及其原理

示例:

	public static void main(String[] args) {
		TestRunnable runnable = new TestRunnable();
		new Thread(runnable).start();
	}

	static class TestRunnable implements Runnable{
		@Override
		public void run() {
			System.out.println("8888888888");
		}
	}

结果:
8888888888

我们可以看到,无论是Runnable还是Callable,它们若想创建一个线程,都离不开Thread,我们进入Thread类:
在这里插入图片描述
有好几个重载的构造方法,这里不一一介绍,也很简单。
我们关注我们使用的:

 public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

进入init方法后,可以看到:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {

		//...
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
		//...

初始化了成员变量target ,其他都不用看了。我们再看到Thread类下的run方法:

 @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

显然,调用了我们实现runnable接口的方法。而Thread的run()方法又由谁调用呢?好了,咱们点到为止,再追究下去就是C/C++了。不信,你看Thread的start方法:

public synchronized void start() {
	//...
        boolean started = false;
        try {
            start0();
            started = true;
	//...

 private native void start0();

被native 修饰的方法,都是底层C++语言实现的了,因此也证明一点,java语言本身是无法创建线程的。

三、适配器模式

为什么要介绍适配器模式呢?

因为Callable、FutureTask实现的代码设计采用的就是适配器模式,因此需要先提前了解一下什么是适配器,不难。

下面的内容都是从隔壁:http://c.biancheng.net/view/1361.html盗过来的小部分,懒得写了哈哈,想详细了解的可以去看看。

  1. 模式的结构
    适配器模式(Adapter)包含以下主要角色。
    目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
    适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
    适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式的结构图如图 1 所示。

在这里插入图片描述
代码:

package adapter;
//目标接口
interface Target
{
    public void request();
}
//适配者接口
class Adaptee
{
    public void specificRequest()
    {       
        System.out.println("适配者中的业务代码被调用!");
    }
}
//类适配器类
class ClassAdapter extends Adaptee implements Target
{
    public void request()
    {
        specificRequest();
    }
}
//客户端代码
public class ClassAdapterTest
{
    public static void main(String[] args)
    {
        System.out.println("类适配器模式测试:");
        Target target = new ClassAdapter();
        target.request();
    }
}

适配器简单总结:通俗的说就是,客户端client调用适配器ClassAdapter的request()方法,而适配器ClassAdapter的request()方法中调用了适配者Adaptee的request()方法。

简单了解了适配器模式后,下面开始进入Callable、FutureTask的源码解析。

四、Callable实现原理

再贴一下Callable例子的代码:

public class Test {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		TestCallable testCallable = new TestCallable();
		FutureTask<String> futureTask = new FutureTask<>(testCallable);
		new Thread(futureTask).start();

		String s = futureTask.get();
		System.out.println(s);
	}

	static class TestCallable implements Callable<String>{
		@Override
		public String call() throws Exception {
			System.out.println("call execute");
			return "success";
		}
	}

}

懂得了适配器之后,我们以适配器的概念来拆分这些代码。

1、目标(Target)接口是Callable接口:

public interface Callable<V> {
    V call() throws Exception;
}

2、适配者(Adaptee)类,我们需要实现的业务逻辑

static class TestCallable implements Callable<String>{
		@Override
		public String call() throws Exception {
			System.out.println("call execute");
			return "success";
		}
	}

3、适配器类是FutureTask

public class FutureTask<V> implements RunnableFuture<V> {
	//...
	
	//初始化适配器,将适配者注入,并记录一个线程的新建状态(用于后面获取call返回值时,判断线程是否执行结束)
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

	//...

 //实现runable接口run方法,实现逻辑是调用了适配者的run()方法,并收集结果,改变state 的状态值
 public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.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);
        }
    }
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

这里只贴了重要的几个方法。可以看到适配者通过FutureTask futureTask = new FutureTask<>(testCallable);注入进了适配器中,而适配器FutureTask所实现的run()方法,其实是调用了testCallable的run()方法。

4、客户端类是Thread

public class Thread implements Runnable {
	//...

   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
	//...
}

前面已经介绍过Runable的实现原理,因此此处也是同理的。

经上述的分析,我们已经知道了,new Thread(futureTask).start();是如何调用到咱们的call()方法了。

本人尝试画了下面一个类图,方便大家理解:

在这里插入图片描述

五、futureTask.get()获取值

事情还没结束,再看futureTask.get()做了啥,如何拿到我们要的结果?
该方法的实现:

public class FutureTask<V> implements RunnableFuture<V> {

	//...省略...
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
	//...省略...

	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();
            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);
        }
    }

   static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
}

重点是awaitDone()这个方法,看着也挺复杂,咱们也点到为止,简单理解该方法:它先是拿到当前线程,然后再去取结果,取不到就阻塞,取到了就返回,执行结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值