理解java中的Callable、Future及FutureTask

Callable

Callable与Runnable类似,用于封装一个异步运行的任务,但是有返回值。Callable接口是一个参数化的类型,只有一个call(),运行一个将产生结果的任务。

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

类型参数是返回值的类型。例如,Callable<Integer>表示一个最终返回Integer 对象的异常计算。

Future

Future 保存异步计算的结果,可以启动一个计算,将Future对象交给某个线程,Future对象的所有者在结果计算好接可以获得它。
Future 接口具有下面的方法:

public interface Future<V>
{
    V get() throws ...;
    V get(long timeout,TimeUnit unit) throws ...;
    void cancel(boolean mayInterrupt);
    boolean isCancelled();
    boolean isDone();
}

V get()
V get(long time, TimeUnit unit)
获取结果,如果没有结果可用,则阻塞直到真正得到结果或者超过指定的时间为止。如果不成功,第二个方法会拋出 TimeoutException 异常。如果运行该计算的线程被中断,两个方法都将拋出 IntermptedException。如果计算已经完成,那么get方法立即返回。

boolean cancel(boolean mayInterrupt)
尝试取消这一任务的运行。如果任务已经开始,并且mayInterrupt参数值为 true,它就会被中断。如果成功执行了取消操作,返回 true。

boolean isCancelled()
如果任务在完成前被取消了,则返回 true。

boolean isDone()
如果任务结束,无论是正常结束、中途取消或发生异常,都返回 true。

FutureTask

FutureTask包装器是一种非常便利的机制,可将Callable转换成Future和Runnable,它同时实现二者的接口。例如:

Callable<Integer> callable=...;
FutureTask<Integer> task=new FutureTask<Integer>(callable);
Thread t=new Thread(task);//it's a Runnable
t.start();
...
Integer result=task.get();//it's a Future

下面结合代码对Callable、Future及FutureTask进行综合运用

假如现在有个需要读取手机外部存储中文件数量的任务。

先采用多线程(Thread+Runnable)的方式对外部目录进行递归查询,对每个子目录创建一个线程读取文件数量,还需要创建一个外部变量用于保存读取到的文件数量,考虑到多线程安全及临界区的问题所以对这个变量操作时需使用锁。这里采用在间隔的时间内读取fileCount是否变化来确定文件数量是否读取完毕(仅仅用于测试,实际使用不建议采用这种方法)。
主要代码如下:

    private volatile int fileCount=0;
    private Object lock=new Object();

    void ThreadRunnableTest(){
        File filePath = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory() : getCacheDir();
        LogUtil.i("ThreadRunnableTest", "filePath:" + filePath.toString());
        readFileNum(filePath);
        new Thread(new Runnable() {
            @Override
            public void run(){
                int temp=-1;
                while(temp!=fileCount)
                {
                    temp=fileCount;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                LogUtil.i("ThreadRunnableTest","文件数量:"+fileCount);
            }

        }).start();
    }

    void readFileNum(final File directory){
        new Thread(new Runnable() {
            @Override
            public void run() {
                File[] files = directory.listFiles();
                for(File file:files){
                    if(file.isDirectory()){
                        readFileNum(file);
                    }else{
                        synchronized (lock){
                            fileCount++;
                        }
                    }
                }
            }
        }).start();
    }

Log信息:

这里写图片描述

CPU占用率:
这里写图片描述


可以看到在上面的多线程例子中有一些不如意的地方:

  • 虽然是采用多线程并行读取各子目录列表,但每个线程将读取的文件数传递给fileCount时需要用锁保证线程安全,相对于fileCount变量来讲其实质还是串行操作。
  • 线程结束后无法及时获取到结果。

对于读取手机外部存储中文件数量这个任务,采用FutureTask、Future以及Callable来实现则会优雅的多,在call方法内部,使用相同的递归机制,对于每一个子目录,产生一个新的MatchCounter并为它启动一个线程,此外,把FutureTask对象隐藏在ArrayList中。最后把所有的结果加起来。并且可以采用Cancel()方法取消这些任务的运行。
下面为测试FutureTask、Future以及Callable的一个例程:

/**
* Author  :  Makesky
* File    :  FutureActivity.java
* Intro   :  读取外部SD卡文件数量
* Version :  1.0
* Date    :  2018/4/25 20:57
*/
public class FutureActivity extends AppCompatActivity {

    String TAG = getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_future);

        new Thread(new Runnable() {
        @Override
        public void run() {

            File filePath = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ? Environment.getExternalStorageDirectory() : getCacheDir();
            LogUtil.i(TAG, "filePath:" + filePath.toString());
            MatchCounter counter = new MatchCounter(filePath, null);
            FutureTask<Integer> task = new FutureTask<>(counter);
            Thread t = new Thread(task);
            t.start();
            try {
                LogUtil.i(TAG, "文件数量:" + task.get());

            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }).start();
    }


    class MatchCounter implements Callable<Integer> {

        private File directory;
        private String keyword;

        public MatchCounter(File directory, String keyword) {
            this.directory = directory;
            this.keyword = keyword;
        }

        @Override
        public Integer call() throws Exception {
            int count = 0;
            try {
                File[] files = directory.listFiles();
                List<Future<Integer>> results = new ArrayList<>();

                for (File file : files) {
                    if (file.isDirectory()) {
                        MatchCounter counter = new MatchCounter(file, keyword);
                        FutureTask<Integer> task = new FutureTask<Integer>(counter);
                        results.add(task);
                        Thread t = new Thread(task);
                        t.start();
                    } else {
                        count++;
                    }
                }

                for (Future<Integer> result : results) {
                    try {
                        count += result.get();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return count;
        }
    }

}

上边的这个例程用于读取外部存储的文件数量,由于虚拟机上一般没有外部存储,在虚拟机上读到的文件数量为0,建议在Android手机上运行一下,时间可能会有点久,视文件数量决定,大概在几十秒的左右。

Log信息:

这里写图片描述

CPU占用率:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/makercloud/article/details/80230790