java多线程高级-FutureTask与Callable(八)

java多线程高级-FutureTask与Callable(八)

平时在工作中一般用到的多线程为:一种是直接继承Thread,另外一种就是实现Runnable接口,但是有时我们希望多线程执行完后返回结果,那就使用FutureTask与Callable。

FutureTask

从图中可以看出,FutureTask只是一个实现了Runnable的一个普通类,没什么特别的,唯一多了一个Future接口的实现就是。
Future 是一个接口,只是在Runnable的基础上,定义了几个扩展动作而已,至于你怎么实现,完全可以自定义,当然你也可以使用官方的FutureTask实现类来直接获取返回值。

Future(转)

Future对象本身可以看作是一个显式的引用,一个对异步处理结果的引用。由于其异步性质,在创建之初,它所引用的对象可能还并不可用(比如尚在运算中,网络传输中或等待中)。这时,得到Future的程序流程如果并不急于使用Future所引用的对象,那么它可以做其它任何想做的事儿,当流程进行到需要Future背后引用的对象时,可能有两种情况:

  • 希望能看到这个对象可用,并完成一些相关的后续流程。如果实在不可用,也可以进入其它分支流程。
  • “没有你我的人生就会失去意义,所以就算海枯石烂,我也要等到你。”(当然,如果实在没有毅力枯等下去,设一个超时也是可以理解的)

对于前一种情况,可以通过调用Future.isDone()判断引用的对象是否就绪,并采取不同的处理;而后一种情况则只需调用get()或get(long timeout, TimeUnit unit)通过同步阻塞方式等待对象就绪。实际运行期是阻塞还是立即返回就取决于get()的调用时机和对象就绪的先后了。

简单而言,Future模式可以在连续流程中满足数据驱动的并发需求,既获得了并发执行的性能提升,又不失连续流程的简洁优雅。

但是Futrue模式有个重大缺陷:当消费者工作得不够快的时候,它会阻塞住生产者线程,从而可能导致系统吞吐量的下降。所以不建议在高性能的服务端使用。

Future定义了几个方法:

  • cancel方法用来取消任务,如果取消任务成功则返回true。
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone方法表示任务是否已经完成,若任务完成,则返回true;
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。 也就是说Future提供了三种功能:
    1)判断任务是否完成;
    2)能够中断任务;
    3)能够获取任务执行结果。

如果使用了Future中的get会产生阻塞,这里有点类似CountDownLatch一样,你去田里面干活,我在家做饭,要吃饭的时候我等你一起吃饭。
注意:只有在调用Future中的get时才会阻塞线程,所有对于主线程来说,越晚调用get获取返回值越有利。

自己实现Future

看了上面的理论,在看了下源码,发现原来在多线程下获取返回结果是这样的。知道了原理之后为什么不自己实现一个FutureTask呢?

FutrueTaskDome为自己简单实现的一个线程返回结果的类。

package cn.thread.first.callable.myself;

import java.util.concurrent.*;
import java.util.concurrent.locks.LockSupport;

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


    private Callable<V> callable;
    private Object outcome;
    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 Thread thread = Thread.currentThread();


    public FutureTaskDome(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    public void run() {
        if (state != NEW) {
            return;
        }
        V result;
        try {
            result = callable.call();
            System.out.println("result==" + result);
        } catch (Exception e) {
            e.printStackTrace();
            result = null;
            setException(e);
        }
        set(result);
        finishCompletion();

    }

    protected void set(V v) {
        //UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)
        System.out.println("set==1="+state);
        state = COMPLETING;//运行中
        outcome = v;
        state = NORMAL;//运行完毕
        System.out.println("set==2="+state);

    }

    private void finishCompletion() {
        Thread t = thread;
        if (t != null) {
            thread = null;
            System.out.println("unpark.....");
            LockSupport.unpark(t);

        }
    }

    protected void setException(Throwable t) {

        state = COMPLETING;//运行中
        System.out.println("setEx" + state);
        outcome = t;
        state = EXCEPTIONAL;//运行完毕
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
        return false;
    }

    public boolean isCancelled() {
        return false;
    }

    public boolean isDone() {
        return state != NEW;
    }

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING) {
            System.out.println("get1==" + s);
            for (;;){
                s = state;
                System.out.println("for==" + s);
                if (s > COMPLETING) {
                    break;
                }else if (s == COMPLETING){
                    Thread.yield();
                }
                LockSupport.park();
            }
        }

        System.out.println("get=report==");
        return report(s);
    }

    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return null;
    }

    private V report(int s) throws ExecutionException {
        System.out.println("report==="+s+",outcome="+outcome);
        Object x = outcome;
        if (s == NORMAL)
            return (V) x;
        throw new ExecutionException((Throwable) x);
    }
}

DomeMain主线程

package cn.thread.first.callable.myself;


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

class DomeService implements Callable<String> {

    private String name;

    public DomeService(String name) {
        this.name = name;
    }

    public String call() throws Exception {
        System.out.println("---" + name);
        return "我的名字叫:" + name;
    }
}


public class DomeMain {

    public static void main(String args[]) {

        Callable<String> callable = new DomeService("bww");

        FutureTaskDome<String> futureTaskDome = new FutureTaskDome<String>(callable);

        Thread thread = new Thread(futureTaskDome);
        thread.start();

        try {
            //Thread.sleep(2000);
            String stringFuture = futureTaskDome.get();
            System.out.println(stringFuture);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

先来看下结构图,这样就明白了。

解说:

  1. FutureTaskDome实现了Runnable,所以在使用thread.start()会开启一个子线程来处理FutureTaskDome中run()方法。
  2. 主线程中操作了futureTaskDome.get()相当于同步调用了一个方法而已,而get里面有一个判断逻辑,如果state<2就会阻塞,这里阻塞的是当前线程,当前线程是主线程啊,所以主线程就被阻塞了。

    图中main方法里面的1,2步是异步的哦,1,2步没有任何关联,start()会开启一个线程,单独去处理run()方法。假设没有调用start开启一个线程,而是直接执行futureTaskDome.run(),方法呢。是不是感觉就很简单了,第一步操作了run,run方法里面处理完毕任务,然后执行第二步顺利拿到结果完事。

    这里想多说两句,我们平时的变成时顺序执行,很好理解,而thread.start()方法开启了一个单独的线程去执行了run方法。thread.start()无非就是我一个人盖房子太慢了,执行thread.start()找了了一个帮手,我们一起盖房子,我们两个同时干活。所以这里的start()后,只是说futureTaskDome.run()和主线程一起执行了而已,没有什么好特别的。

  3. DomeService类实现了Callable,DomeService里面的run()方法才是我们真正要处理的任务。但并没有为它开启一个单独的线程去执行,而是直接调用它的run()方法。真正单独开启了一个线程的是FutureTaskDome这个对象,而FutureTaskDome这个对象的run方法再调用DomeService里面的run()方法去处理任务。

    FutureTaskDome就好像一个代理一样。

  4. futureTaskDome.get() 这个里面有一个判断,state<2时,阻塞当前线程。谁是当前线程呢?那就是主线程了,因为是主线程调用的get方法。

    LockSupport.park();这个在重温一遍,哪个线程调用它,它就阻塞哪个线程。这里是主线程调用了它,所以阻塞了主线程。
    private Thread thread = Thread.currentThread();这里的当前线程怎么算呢?谁是实例化了它,Thread.currentThread()就是它了。这里是主线程里面实例化了它,所以thread变量就是主线程。
    如果把Thread.currentThread()写到futureTaskDome.run()里面或者callable.run()里面,代表的就是子线程futureTaskDome了,因为futureTaskDome.run()是开启了一个新的线程,而这个新的线程又调用了callable.run()方法,所以他们是一条线的上的。


这里有两个线程:圈圈标记的1,2是两个线程。2是1的子线程。

“问题” :为什么子线程2里面run()改变了state=2,而1线程get()的时候能读到state=2呢?
因为线程1,2操作的是同一个对象futureTaskDome。哈哈哈哈

Callable

看到好多文章拿Callable与Runable来做对比,或者说问一些他们之间的区别,还长篇大论的讨论,其实他们就是一个接口而已,仅仅一个接口而已,没什么别的了。
Runnable是java jdk原生定义的,如果实现了Runnable,那么在Thread.start()就会创建一个新的线程来调用Runaable实现类的run方法。仅此而已。
而Callable则是rt.jar里面的扩展接口,统一接口规范而已。

源码解读

其实上面也是参照源码实现的,这里讲解源码已经不是很必要了,但是也解说一下吧。
Callable,Future,都是接口没啥将的,主要是将一下实现类FutureTask。

成员变量:

    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;//中断结束状态

    //这个就好比一个代理,接受外面传递进来的callable,然后这里代理它执行run方法。
    private Callable<V> callable; 

    //用来阻塞调用放的线程用的,谁调用我,我就阻塞谁。下面会有讲解。
    private volatile Thread runner; 

    //调用get方法获取返回值时,一个阻塞内部类
    private volatile WaitNode waiters;
//实例化的时候
public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}

//开启线程调用run方法时:
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 {
                    //代理执行外面传递进来的callable的call方法。
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    //返回线程内部异常信息。
                    setException(ex);
                }
                //调用set设置返回值,解锁阻塞线程,更改状态
                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);
        }
}

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

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
    }

UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)这个是个调用本地方法的cas操作。将COMPLETING变量值设置给state,如果设置成功返回true,如果设置失败返回false。这里怎么就操作了state了呢?看下面,这里是通过指针偏移来操作的,具体不是很懂,太底层了,直到它是设置state的值就好了。CAS前面文章也讲过了,这里不再赘述。

private static final sun.misc.Unsafe UNSAFE;
    private static final long stateOffset;
    private static final long runnerOffset;
    private static final long waitersOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
            runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
            waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

上面是整个子线程里面的操作,就是执行run任务,然后设置状态,没了。那么现在来看看主线程调用xxx.get()方法获取返回值的时候干了什么事。

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //是不是和我上面写的一摸一样,看看是否还在执行,如果s<=1则表示线程正在执行,进入等待中。
        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;//再次获取state的状态
            if (s > COMPLETING) {//在判断一次。
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) //如果s==1表示正在执行,调用yield,让其他线程先执行。注意:yield只是客气一下,并不一定会产生实质性结果,就是说并不一定会真的让出CPU,让其他线程占用。这个和暂停1毫秒差不多,我的理解啊。
                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;
                }
                //get的时候是否设置了时间,设置了时间,过了这个时间则自动解锁不在等待返回结果。
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);//阻塞调用get()方法的线程。这个park是阻塞当前调用get的线程和this参数无关哦。
        }
    }    

参考

http://blog.csdn.net/u010412719/article/details/52137262
http://zha-zi.iteye.com/blog/1408189

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81562304