线程是面试时经常问到的问题,今天来说下线程的第三种创建方式。
说起线程,大家都熟悉如何新建并启动一个线程,立马想到的有这两种
new Thread().start()
new Thread(new Runnable(){
@Override
public void run(){
...
}
}).start()
可以总结为第一种是直接new或者继承来重写Thread的run()方法来实现线程完成的任务,第二种则是通过实现了Runnable接口的类来实现线程操作。
还有第三种启动方式不知道大家了解么,就是FutureTask
经过前两篇关于Thread的介绍,我们知道在Android里子线程是不允许直接操作主线程的,因此需要通过Handler来跟主线程交互 深入理解Android的Looper 这样一来前两种Thread的启动方式在代码上会显得不够灵活,因为我们需要在run()里面最后进行数据处理和分发。通常在多任务并发的场景我们就需要另外的方法了。
Future很好的解决了这个问题。Future是个接口,实现了这个接口的唯一类是FutureTask。 在多并发的场景中,通常会用线程池来管理Thread,而用FutureTask来包装具体的线程操作。 一个常见的代码可能如下
public class FeatureTaskSample {
private static final String TAG = "FeatureTaskSample";
private ExecutorService executorService = Executors.newFixedThreadPool(5);
private List<FutureTask<String>> taskList = new ArrayList<FutureTask<String>>();
public void executeTask() {
for(int i = 0; i < 10; i++) {
FutureTask<String> task = new FutureTask<String>(new CallableSample(i));
taskList.add(task);
executorService.submit(task);
}
for(FutureTask<String> task : taskList){
try {
Log.d(TAG, "executeTask: " + task.get());
} catch (InterruptedException exp) {
//do something
} catch (ExecutionException exp) {
//do something
}
}
}
class CallableSample implements Callable<String> {
int num;
public CallableSample(int i) {
num = i;
}
@Override
public String call() throws Exception {
Thread.sleep(1000);
String result = String.valueOf(this.num);
String currTime = String.valueOf(System.currentTimeMillis());
return result + "_" + currTime;
}
}
}
FutureTask同时实现了Runnable和Future接口,而Executor可以接受实现了Runnable接口的对象,因此可以把FutureTask提交给线程池去执行。 FutureTask的run()接口在被线程调用后,会去调用构造时传入的Callable对象,在我们的代码里就是CallableSample的call()方法。 Future的get()接口呢,其实是个阻塞方法,它会在等到call()方法结束后才会返回。因此如果在Android的主线程中调用,会造成线程阻塞。对于这种情况,下面的代码是更好的解决办法
public class SampleFutureTask {
private static final String TAG = "SampleFutureTask";
private ExecutorService executorService = Executors.newFixedThreadPool(5);
private List<FutureTask<String>> taskList = new ArrayList<FutureTask<String>>();
public void executeTask() {
for(int i = 0; i < 10; i++) {
FutureTask<String> task = new FutureSample(new CallableSample(i));
taskList.add(task);
executorService.submit(task);
}
}
class FutureSample extends FutureTask<String> {
Callable<String> callable ;
public FutureSample(@NonNull Callable<String> callable) {
super(callable);
this.callable = callable;
}
@Override
protected void done() {
try {
Log.d(TAG, "executeTask: " + this.get());
} catch (ExecutionException e) {
} catch (InterruptedException exp) {
}
}
}
class CallableSample implements Callable<String> {
int num;
public CallableSample(int i) {
num = i;
}
@Override
public String call() throws Exception {
Thread.sleep(1000);
String result = String.valueOf(this.num);
String currTime = String.valueOf(System.currentTimeMillis());
return result + "_" + currTime;
}
}
}
这里面构造了一个内部类重载了FutureTask的done()方法,这个方法在call()结束后会调用,这样就不用等我们主动去调用get()方法并等待线程返回了。而Android中的AyncTask用的也是这种思路,AsyncTask多做的部分是在get()到数据后,通过Handler把数据丢还到主线程,并在onPostExecute()中去处理。
下面是这段代码的输出结果
D/SampleFutureTask: executeTask: 0_1523847353651 D/SampleFutureTask: executeTask: 2_1523847353651 D/SampleFutureTask: executeTask: 1_1523847353652 D/SampleFutureTask: executeTask: 3_1523847353652 D/SampleFutureTask: executeTask: 4_1523847353653 D/SampleFutureTask: executeTask: 5_1523847354651 D/SampleFutureTask: executeTask: 6_1523847354652 D/SampleFutureTask: executeTask: 7_1523847354652 D/SampleFutureTask: executeTask: 8_1523847354653 D/SampleFutureTask: executeTask: 9_1523847354654
采用实现Runnable、Callable接口的方式创见多线程时 · 优势是: 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 · 劣势是: 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时 · 优势是: 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。 · 劣势是: 线程类已经继承了Thread类,所以不能再继承其他父类。