Java源码学习之高并发编程基础——多线程的创建(下)

1.前言&目录

前言:

在上一篇文章Java源码学习之高并发编程基础——多线程的创建(上)中,讲解了Java中多线程的创建(无返回值)以及基本的运作机制,但是还有其他方式同样可以实现多线程,比如有返回值的FutureTask。

因此,本文将继续以类和接口源码解读的方式,讲解有返回值的多线程编程方式——FutureTask。

目录:

1.前言&目录

2.源码分析

 2.1  Future接口源码剖析

 2.2  RunnableFuture接口源码剖析

 2.3  Callable接口源码剖析

 2.4  FutureTask类源码剖析

2.4.1 FutureTask#run()方法源码剖析

2.4.2  FutureTask#get()方法源码剖析

2.4.3 FuturueTask#cancel()方法

2.4.4 FutureTask#isCancelled()方法

2.4.5 FutureTask#isDone()方法

2.5  实现有返回值的多线程

3.总结

2.源码分析

Java源码学习之高并发编程基础——多线程的创建(上)中,详细介绍了线程的运行逻辑,本质上无论是继承Thread还是实现Runnable接口,最终的底层实现都是需要借助Thread#start()方法去调用C语言方法,通过它让JVM创建更小的执行单元去执行run()方法,这就是多线程编程的基本原理。

但是后来C语言没有实现有返回值的多线程,而是Java1.5版本以后,通过创建了几个接口、类完美的实现了获取多线程执行的返回值。这些接口和类的设计恰到好处,即充分体现了面向对象的特性,实际上也运作的很好。

这些接口和类分别是Future接口、RunnableFuture接口、Callable接口、FutureTask类,接下来将逐一分析它们的源码。

2.1  Future接口源码剖析

Future接口是一个泛型接口,其目的是为了接受多线程中不同类型的返回结果。那么它存在的意义是什么?

实际上,在Java中定义接口,是为了定义一个规范。如果你想获取多线程执行的结果那么就必须实现此接口。并且在实践中,线程池中处理有返回值的多线程也是用Future去接收返回结果的。

如下,它一共有五个抽象方法,比较常用的获取返回结果的有两个get方法,一个是没有设置超时时间的,即会一直阻塞到多线程的结果返回。另一个则是设置了超时时间,即最多会等待timeout的时间,超过时间仍然没有获取到结果则会抛出TimeoutException。

public interface Future<V> {
    // 尝试取消任务执行,前提是任务处于可以被取消的状态
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();// 如果此任务在正常完成前已取消,则返回true
    boolean isDone(); // 判断当前任务是否完成(包含计算中,正常完成,发生异常) 
    // 没有超时时间的获取方法,会一直阻塞到结果返回
    V get() throws InterruptedException, ExecutionException;
    // 有超时时间的获取方法,如果超时时间已过还没拿到结果则抛出异常
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

剩下三个方法,由于不同的实现类有不同的实现,因此不在这里进行深入讲解,将在后面讲解2.4  FutureTask类源码剖析时深入分析,但注意它们的返回值都是布尔类型。 

2.2  RunnableFuture接口源码剖析

如果说Future接口是定义能获取多线程执行结果的规范,那么这个RunnableFuture接口的作用就是将Thread和Future衔接起来。

 在Java中,多线程的执行有且仅有一种方式,就是通过调用Thread实例start()方法进而调用native的C语言方法,让其在操作系统层面创建一个更小的执行单元去执行Thread实例的run()方法。

但是无论在Thread#run()还是Runnable#()run()方法,都是无返回值的。因此在Java1.5以后,为了支持能获取多线程编程中的返回结果,定义了Future接口。为了可以接入到Thread,也定义了一个RunnableFuture的泛型接口。

如下伪代码所示,它继承了Runnable接口和Future接口。其目的就是为了即能借助Thread去实现多线程编程也能获取返回结果。

public interface RunnableFuture<V> extends Runnable, Future<V> {
    // 有返回值的多线程在此方法设置返回值
    void run();
}

继承Runnable接口,意味着实现RunnableFuture接口的实现类本质上也是一个Runnable实例,可以直接将其作为Thread的构造函数入参去创建一个Thread实例。

继承Future接口,意味着实现RunnableFuture接口的实现类必须重写其抽象方法,其中就包括两个获取返回的结果的get方法。

后面将要讲解的FutureTask正是实现了RunnableFuture接口,也就是说FutureTask即承担着Thread实例的成员属性(Runnable)target目标对象的角色、也拥有获取返回结果的能力。

2.3  Callable接口源码剖析

Callable接口的设计正是为了能获取多线程编程的返回结果而生,它是一个泛型接口,也就是说实现此接口的类必须明确返回结果类型。

 从结构上看,它和Runnable接口非常相似,两者都是函数式接口(即可以直接使用lamada表达式去代替创建一个类并重写抽象方法)。

不同的是,Runnable#run()方法没有返回值并且也没声明异常,Callable#call()有返回值并声明了受检异常,后者的代码健壮性更好。

@FunctionalInterface
public interface Callable<V> {
    // 有返回值的方法,发生异常将抛出
    V call() throws Exception;
}

在功能上,多线程编程想要获取返回结果必须实现此接口,因为在FutureTask类中有一个成员属性private Callable<V> callable,它正是负责执行实际上的多线程业务逻辑,执行完毕后会将结果保存起来。

2.4  FutureTask类源码剖析

Future、RunnableFuture、Callable接口组合起来并应用在FutureTask即可完成能获取返回值的多线程编程。

接下来让我们看看这个FutureTask是如何实现即实现多线程编程、又能获取返回结果的。如下所示, FutureTask是一个泛型类,目的正是为了接受不同类型的执行线程的返回结果。

首先看FutureTask的两个构造函数,我们只需要关注入参是Callable<V> callable的即可,因为通过这个构造参数创建的FutureTask实例在工作时才能保存真实的返回结果。另外一个的返回结果其实就是入参中的V result,并不那么真实(即可以随意设置)。

public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable; // 多线程的执行通过此对象完成
    private Object outcome;  // 结果保存到这个变量中
    // 执行run方法时候会将当前执行的线程对象获取并设置在这里
    private volatile Thread runner; 
    private volatile int state; // FutureTask实例状态
    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;
    public FutureTask(Callable<V> callable) { // 需要真实返回值的,调用这个
       if (callable == null)
           throw new NullPointerException();
       this.callable = callable;
       this.state = NEW;   // ensure visibility of callable
    }
    public FutureTask(Runnable runnable, V result) { // 不需要真实返回值的,调用这个
       this.callable = Executors.callable(runnable, result);
       this.state = NEW;       // ensure visibility of callable
    }
}

在FutureTask(Callable<V> callable)中,先对传入的callable做空判断,不为空时将它设置给其成员变量callable中,同时将当前的state属性为NEW。

state成员变量是用volatile关键字修饰的,目的是为了在不同线程操作时保持可见性。它的值可能是NEW、COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTING、INTERRUPTED,七种状态值的范围是从0到6逐渐递增1.

每种状态表示不同的情况,比如NEW则是表示当前FutureTask的任务还没执行或者正在执行中,COMPLETING则是从NEW变为NORMAL或EXCEPTIONAL时的中间状态,NORMAL则是任务执行成功,得到返回结果后设置的状态。EXCEPTIONAL则是在任务执行过程中发生了异常,需要设置状态为EXCEPTIONAL标记执行失败。

这里说的任务执行,其实callable调用call()方法的过程,那么这个成员属性callable什么时候会派上用场呢?

2.4.1 FutureTask#run()方法源码剖析

callable其实是在FutureTask重写的run()方法用到的。

 如下伪代码所示,在FutureTask#run()方法中,会判断当前state的数值以及能否将当前执行的线程对象(即JVM为当前FutureTask实例分配的一个运行单元)设置到runner成员属性。只有状态为NEW并且通过Thread.currentThread()成功获取当前线程对象设置到runner成员属性时,才会接着进入下一步。runner属性代表着执行当前FutureTask实例的run()的运行单元,在后面的2.4.3  FuturueTask#cancel()方法可能会被使用到,目的是为了中断当前线程的执行。

接着便是判断当前的callable成员属性是否为空并且状态为NEW,然后创建一个泛型变量result去接收callable调用call()方法后的返回结果。

如果期间没有发生异常,则调用set方法将call()方法的返回结果保存到FutureTask实例的成员变量(Object) outcome中,后面调用get方法获取返回结果也是从此outcome获取。

public class FutureTask<V> implements RunnableFuture<V> {
    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) {
                    ...
                    setException(ex);
                }
                if (ran)
                    set(result);// 设置结果到outcome属性
            }
        } finally {
            ... 
        }
    }
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;// 设置结果到outcome属性
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
    protected void setException(Throwable t) {
       if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
           outcome = t;
           UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
           finishCompletion();
       }
    } 
}

在set方法中,会先原子地将当前的state状态从NEW转为COMPLETING,成功设置后再将返回结果赋值给outcome变量,然后又将state状态从COMPLETING设置为NORMAL,表示任务成功执行。

但是如果在执行call()方法时发生了异常,则会将捕获到的异常对象传递并调用setException(ex)的方法,将捕获到的异常对象保存到outcome中,并设置当前的state为EXCEPTIONAL,表示当前任务执行出现了异常。

也就是说,在FutureTask#run()方法中执行callable#call()方法时期间无异常发生时,FutureTask的state状态会经历NEW -> COMPLETING -> NORMAL的变化。如果期间有异常发生,则是NEW -> COMPLETING -> EXCEPTIONAL的变化。

2.4.2  FutureTask#get()方法源码剖析

当FutureTask#run()方法成功执行以后,可以通过FutureTask#get()获取返回结果。

在以下伪代码中可以看到,获取多线程执行的返回结果,提供了两个get方法。

一个是没有设置超时时间的get()方法,即会一直阻塞到能获取返回结果。它是怎么阻塞的?原来它会将当前FutureTask的state去做判断。

如果state一直小于等于COMPLETING,则会一直调用awaitDone(false,0L)阻塞等待,在上个小节2.4.1  FutureTask#run()方法源码剖析讲解的run方法中,state的变化过程(假设一切都正常、没有报错)是从NEW -> COMPLETING -> NORMAL,这三者对应的数值分别是0、1、2。也就是说,只有通过set方法将返回值设置给outcome成员变量以后才会将state变成NORMAL,这时候才会返回复制的outcome变量值。

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);
    }
    public V get(long timeout, TimeUnit unit)
       throws InterruptedException, ExecutionException, TimeoutException {
       if (unit == null)
           throw new NullPointerException();
       int s = state;
       if (s <= COMPLETING &&
           (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
           throw new TimeoutException();
       return report(s);
    }
    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);
    }
}

另一个则是get(long timeout, TimeUnit unit)方法,在伪代码中可以看到,如果在state的数值小于等于COMPLETETING时(即run方法没执行时或者正在执行但仍然没有进入到set方法设置返回值时)并且等待的时间已经超过了timeout,则会抛出一个TimeoutException。

从应用程序的可用性来说,更推荐使用带超时时间的get方法,如果发生了未知异常,不带超时时间的get方法会一直阻塞到能获取返回结果。

2.4.3 FuturueTask#cancel()方法

在FuturueTask的cancel(boolean mayInterruptIfRunning)方法中,会尝试停止执行任务。

首先,会判断此时FuturueTask实例的state状态,在分析run()方法2.4.1 FutureTask#run()方法源码剖析的时候已经讲过,执行callable#call()方法的前提是state为NEW。

而在这里,如果状态不是NEW或者实际上的state不等于预期的NEW的话因而没有成功重新将state设置为INTERRUPTING或者CANCELLED的话,则会返回false。表示已经无法取消此任务的执行。那么这种情况是怎么样的呢?

回顾FuturueTask#run()方法的源码,无论callable#call()方法执行过程中有无发生异常,它在没有调用到set()、setException()之前状态都是NEW,只有进入了才会先设置为COMPLETING,接着再设置为NORMAL(任务执行无报错)或者EXCEPTIONAL(任务执行有报错)。

public class FutureTask<V> implements RunnableFuture<V> {
   public boolean cancel(boolean mayInterruptIfRunning) {
      if (!(state == NEW &&
            UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
          return false;
      try {    // in case call to interrupt throws exception
          if (mayInterruptIfRunning) {
              try {
                  Thread t = runner;
                  if (t != null)
                      t.interrupt();
              } finally { // final state
                  UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
              }
          }
      } finally {
          finishCompletion();
      }
      return true;
    }
}

也就是说,只要没进入set方法或者setException方法时,就可以尝试取消当前线程的任务执行。

当然传入的mayInterruptIfRunning布尔变量需要为true,接着可以看到它会获取FutureTask实例的runner成员变量,在2.4.1 FutureTask#run()方法源码剖析小节中讲过,run()方法执行前会判断当前state是NEW并且成功通过Thread.currentThread()获取当前执行中的线程对象赋值给runner变量时才会接着往下执行调用callable#call()方法。

当获取到的当前执行线程对象不为空时,通过调用interrupt()方法尝试中断当前线程的工作(但是并不一定会中断成功,此方法只是标记该线程对象状态为中断),接着在finally语句块中将state设置为INTERRUPTED,表示此FutureTask实例的任务已经中断了。也就是说无法再通过调用start()方法再次创建一个执行单元去执行任务。

2.4.4 FutureTask#isCancelled()方法

FutureTask的isCancelled()方法就是判断当前FutureTask实例的状态是否是已经被取消了或者是中断中或已经中断。

 CANCELLED、INTERRUPTING、INTERRUPTED的值是在2.4.3 FuturueTask#cancel()方法这里会被设置。

如果此时FutureTask的callable的call()方法还没被调用或者正在调用时,通过调用cancel()方法可以尝试中断线程工作,此时会被设置为CANCELLED。

public class FutureTask<V> implements RunnableFuture<V> {
   public boolean isCancelled() {
       return state >= CANCELLED;
   }
}

2.4.5 FutureTask#isDone()方法

FutureTask的isDone()方法仅仅是判断当前FutureTask的state是否不为NEW。

即是否是已经调用了通过run()方法调用callable#call()方法,如果期间没有保存则进入set(V v)方法将返回结果保存并将state从NEW转为最终的NORMAL。

如果期间有异常发生并被捕获,则进入setException(Throwable t)将异常对象保存到outcome,并将state从NEW转为最终的EXCEPTIONAL。

public class FutureTask<V> implements RunnableFuture<V> {
    public boolean isDone() {
       return state != NEW;
    }
}

2.5  实现有返回值的多线程

2.4  FutureTask类源码剖析小节以源码讲解的方式介绍了实现有返回值的多线程编程需要的类和接口后,我们可以尝试着结合第一部分Java源码学习之高并发编程基础——多线程的创建(上)中的内容去实现的能获取返回值的多线程编程。

首先,我们还是新建Thread实例,接着再创建一个FutureTask实例传递给Thread实例(为什么能当作Runnable实例传入呢?因为FutureTask实现了RunnableFuture接口、RunnableFuture又继承了Runnable接口,即FutureTask可以当作是Runnable实例使用)。

由于FutureTask的构造函数中需要一个Callable的实例,因此我们还需要创建一个实现此接口的类GoodsServiceCallable并重写其call方法,在call方法中我们增加调试语句打印并返回一个int类型的数值。

public class MutipleThreadTest4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<FutureTask<Integer>> futureTaskList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            FutureTask<Integer> future = new FutureTask<>(new GoodsServiceCallable());
            futureTaskList.add(future);
            new Thread(future).start();
        }
        for (int i = 0; i < futureTaskList.size(); i++) {
            System.out.println("获取到返回结果:" + futureTaskList.get(i).get());
        }
    }
    static class GoodsServiceCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("有返回值的线程[" + Thread.currentThread() + "]正在执行");
            return new Random().nextInt();
        }
    }
}
-------------------------输出结果------------------------------
有返回值的线程[Thread[Thread-3,5,main]]正在执行
有返回值的线程[Thread[Thread-2,5,main]]正在执行
有返回值的线程[Thread[Thread-0,5,main]]正在执行
有返回值的线程[Thread[Thread-1,5,main]]正在执行
获取到返回结果:-500969901
获取到返回结果:-739893054
获取到返回结果:-1427862503
获取到返回结果:-2108558952
有返回值的线程[Thread[Thread-4,5,main]]正在执行
获取到返回结果:-896699150
-------------------------输出结果------------------------------

从输出结果看,通过Thread实例的这个代理对象,调用其start()方法后,最终的确会调用到FutureTask#run()方法并进而调用到有返回值的GoodsServiceCallable类的call方法。

输出了包含当前线程名称的打印语句,并且也能通过FutureTask#get()方法获取多线程执行的返回结果。

3.总结

有返回值的多线程实现方式,底层还是依赖于Thread去实现,调用它的start()方法进入让系统分配资源调用run()方法。

FutureTask类实现了RunnableFuture接口(Runnable接口和Future接口的组合),在它的run方法中通过调用Callable实例的call方法并将返回结果保存起来,通过其get方法再去获取返回结果。

可以说实现有返回值的多线程的设计是如此之妙,定义各种标准(接口),将它们组合起来,即能完美设配Thread类也能拥有获取返回结果的能力。

这种设计其实一种设配器模式,通过转换接口(FutureTask实现Runnable接口))使不兼容的组件(Thread的(Runnable)target成员变量)能够协同工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值