谈谈多线程中的Future

为啥会有Future

常见的开启线程方式有两种:

  • 重写Thread的run方法,调用start开启线程。
  • 给Thread传参Runnable实现类对象(需要实现run方法),调用start开启线程。

但是这两种方式都有局限性,那就是run方法虽然运行在子线程中,但是我们无法得知run方法执行的结果。这时又出现了几个相关类Future、FutureTask、Callable。这几个类结合Thread就可实现获取其他线程中执行方法后的返回结果。

相关简介

Future相关主要有两个类FutureTask和Callable其中Future是一个接口FutureTask是其实现类,Callable类似Runnable。这部分就谈谈如何使用以及API介绍。

首先康康怎样用

首先先来上手写法,有如下三步:

1、创建Callable实现类,实现call接口。
2、创建FutureTask实例,传参callable。
3、借助Thread执行,吧Runnable实现类传参给Thread。

        // 1、创建Callable实现类,实现call接口。
        val callable = object : Callable<Int> {
            override fun call(): Int {
                // 运行在子线程中
                val a = 1
                val b = 2
                // 模拟耗时操作
                SystemClock.sleep(1000)
                return a + b
            }
        }
        //2、创建FutureTask实例,传参callable。(其实FutureTask 就是Runnable的实现类)
        val futureTask = FutureTask(callable)
        // 3、借助Thread执行,Runnable实现类传参给Thread。
        Thread(futureTask).start()

        // 其他线程中可阻塞等待获取结果

        val result = futureTask.get()
        println("result:$result") // System.out: result:3

当熟悉了上面就可以这样变换了:

        // Callable 直接以Lambda 作为FutureTask构造参数。
        val futureTask = FutureTask {
            val a = 1
            val b = 2
            SystemClock.sleep(1000)
            a + b
        }
        Thread(futureTask).start()
        val result = futureTask.get()

1、看过上述的用法后我们不难发现,这个和普通开启多线程方式十分类似。二者是通过Thread构造传参Runnable 然后调用start的方式开启子线程。只是Future这里有一点区别,这里需要使用FutureTask这个Runnable实现类,这个类为系统定义好的。
2、创建FutureTask时再提供一个Callable即可,这里就是要在子线程中执行的任务。

接下来康康相关API

Future接口很简单,定义在并发包中的一个类。定义了子类需要实现的接口。


package java.util.concurrent

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();
    
    boolean isDone();
    
    /*
     等待计算结果完成,获取计算结果。会抛出如下异常:
     1、CancellationException
     2、ExecutionException
     3、InterruptedException
    */ 
    V get() throws InterruptedException, ExecutionException;

    /*
     获取执行的结果,最长等待时间为timeout。
     
     可能会抛出以下异常:
     1、CancellationException:当计算被取消
     2、ExecutionException:当计算过程中threw an exception
     3、InterruptedException:当get等待结果时线程被打断。
     4、TimeoutException:get等待到最大的等待时间还未获得计算结果时。
    */
    
    V get(long timeout, TimeUnit unit)
       throws InterruptedException, ExecutionException,TimeoutException;
}

接下来看看FutureTask 常见api:

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

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

public class FutureTask<V> implements RunnableFuture<V> {
...
    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
    }
    
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

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

1、首先看下包,会了解到一些信息,FutureTask也是并发包下的类,并且FutureTask底层基于LockSupport来实现,这点通过get方法可以看出。
2、其次看下类的继承关系FutureTask实现了Runnable, Future接口。所以FutureTask本质也是一个Runnable实现类,只是这个实现类内部进行了自己的包装。
3、最后看下常见的api,几乎实现了Future定义的所有方法,注意下构造的使用,最常用的还是构造传Callable实现类对象。

接下来看看最后一个相关类:

很简单,就是单独定义的一个接口,供FutureTask的构造使用。

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

那么FutureTask是如何调用Callable的呢?这里就需要浅析下源码喽~

public class FutureTask<V> implements RunnableFuture<V> {
···
   // 0、这个成员变量的值在FutureTask构造创建时被赋值。
    private Callable<V> callable;
    //Callable#call 执行的结果
    private Object outcome; 

    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                // 1、执行Callable的call 方法,获取结果。
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                // 2、结果设置给outcome
                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 (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
        // 3、结果设置给outcome
            outcome = v;
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }

        public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
           //阻塞等待,内部通过LockSupport来实现。
            s = awaitDone(false, 0L);
        // 4、通过report取执行结果
        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);
    }
···

}

可见源码乍一看还是很简单的:
1、从表面看FutureTask就是特定的Runnable类,run方法给我们写好了,run内部调用的Callable#call,这个我们自己实现。
2、从整体流程看也很好理解,这里只需知道LockSupport也是并发包下的类,借助这个类我们很好实现同步阻塞。FutureTask#set 、FutureTask#get 流程在👆🏻已经做好了序号顺着很好理解。

实际应用

模拟一个工作中遇到的类似栗子

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        thread {
            //1、子线程中操作bitmap,拿到结果立即更新
            val originBitmap = getBitmap().get()
            runOnUiThread {
                image1.setImageBitmap(originBitmap)
            }
            //2、缩放,保存到本地。
            val scaleBitmap = originBitmap.scale(originBitmap.width / 2, originBitmap.height / 2)
            val path = cacheDir.absolutePath+"/" + UUID.randomUUID().toString() + ".png"
            val file = File(path)
            if (!file.exists()) {
                file.createNewFile()
            }
            try {
                val fos = FileOutputStream(file)
                scaleBitmap.compress(Bitmap.CompressFormat.PNG, 80, fos)
                fos.flush()
                fos.close()
                println("保存到本地:${file.absoluteFile}")
                //System.out: 保存到本地:/data/user/0/com.example.bitmaptest/cache/c04b32b0-59bf-49c2-bfb0-4edf6ada682f.png
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

    }

    private fun getBitmap(): FutureTask<Bitmap> {
        val futureTask = FutureTask {
            //获取Bitmap,作为Callable#call 返回值
            BitmapFactory.decodeResource(resources, R.drawable.img)
        }
        //  线程池开启线程执行Runnable任务
        Executors.newCachedThreadPool().execute(futureTask)
        return futureTask
    }

栗子还是很简单的,只是有一点需要留意这里执行futureTask这个Runnable时并没有直接使用Thread传参Runnable,而是使用了线程池,因为线程池也可以执行Runnable。

小结

之前对Feture的了解仅仅停留在👆🏻“入门写法”的最基础demo阶段,最近工作中碰到了,闲来稍微总结下感觉收获颇多的。又有了深入的了解。

这里还需要点明一点:使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为受影响的线程总会被迫等待。从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

本文到此点到为止,后面的更深入的相关知识就需要以后继续探索了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值