线程是面试时经常问到的问题,今天来说下线程的第三种创建方式。
说起线程,大家都熟悉如何新建并启动一个线程,立马想到的有这两种
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很好的解决了这个问题。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类,所以不能再继承其他父类。
