Thread 第三种创建方式

173 阅读5分钟
原文链接: mp.weixin.qq.com

线程是面试时经常问到的问题,今天来说下线程的第三种创建方式。

说起线程,大家都熟悉如何新建并启动一个线程,立马想到的有这两种

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类,所以不能再继承其他父类。