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占用率: