您的位置:首页 > 移动开发 > Android开发

Android开发者必知的Java知识(四):Java并发编程

2015-04-15 10:00 441 查看
android编程中,并发编程是不可或缺的一部分,几乎所有应用程序的开发中都会用到并发编程,今天我们就来聊聊并发编程的一些事儿,我们先从java对并发编程的基本支持说起。

转载请注明出处: /article/1326066.html

Runnable与Thread

Runnable与Thread相信大家一定都能熟练使用,我们今天在这里就不赘述了,但是仅仅靠使用Runnnable和Thread是不能满足我们并发编程全部需求的,比如我们需要能有效的控制当有大量并发任务时,任务执行的吞吐量,任务的取消等等。Java中还有一些与并发编程密切相关的类,使用它们能更好地帮我们控制并发编程中任务的合理执行,我先用一张图来大致描述一下有哪些类和接口,这些类和接口之间又怎么样的关系。


.

处于UML图最顶层的是一个接口Executor,这个接口有一个方法叫做execute,Executor的这个方法可以将任务的提交和任务如何执行(包括线程的使用细节,调度等)解耦,相比直接创建线程执行任务,java更建议我们使用Executor,例如使用

new Thread(new(RunnableTask())).start()


java更建议我们使用

Executor executor = <em>anExecutor</em>;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());


来调度任务的执行。

Executor

Executor本身并不保证我们的任务一定会在不同的线程上执行,在最简单的实现中,我们的任务可以立即在调用者的线程上执行:

class DirectExecutor implements Executor {
   public void execute(Runnable r) {
     r.run();
   }
}}


这种情况比较少用,更典型的情况是,我们会为每个任务新创建一个线程去执行,

class ThreadPerTaskExecutor implements Executor {
  public void execute(Runnable r) {
     new Thread(r).start();
  }
}}


ThreadPerTaskExecutor只是简单地为我们的任务分配一个线程去执行,如果我们同时需要执行1000个以上的任务,这个executor会同时开启1000+个线程去执行任务,这会给我们的机器造成比较大的开销,而且每个任务执行的效率也会很低下。因此我们需要在自己的Executor中,增加线程调度的逻辑,其中最简单地调度逻辑就是,每次开启一个线程执行一个任务,等待任务执行完毕,执行下一个任务。

class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                executor.execute(mActive);
            }
        }
    }


每次execute一个runnable时,我们会将runnable重新包装,放入mTasks这个队列中,如何包装?就是在runnable结束之后调用scheduleNext从mTasks重取出一个新的任务并立即执行。AsyncTask中默认的执行方式就是这种。

Executor给我们规定的接口实在太少,接口ExecutorService则继承了Executor接口。

ExecutorService

ExecutorService接口在Executor的基础上,提供了更多的接口,用来管理任务的终止,而且提供了函数用来构造一个Future对象来追踪任务的执行程度。

ExecutorService内的接口函数主要有三种

跟shutdown相关的

shutDown() 这个函数试图shutdown当前executor,拒绝新任务的提交,但是会等待之前提交的任务执行完毕

shutDownNow(), 类似shutDown,区别在于它会终止当前正在执行的任务和正在等待执行的任务

isShutDown() 判断是否已经shutdown,

isTerminated() 判断是否shutdown之后,所有任务已经执行完毕,必须先调用shutdown…

invoke和await

invokeAll 执行给定任务集,当所有任务完成之后,返回一个包括任务状态和结果的Future集合

T invokeAny执行给定任务集,如果某个任务执行完毕,返回执行结果

boolean awaitTermination(long timeout, TimeUnit unit) 这个函数会阻塞,直到在shutdown请求后所有任务执行完毕,或者超时 或者线程异常终止

submit系列

Future submit(Callable task); 提交一个callable任务,并返回一个Future对象,从Future对象可以阻塞地获得执行结果。如果想在执行任务的时候立马阻塞等待结果,可以这样做

result = exec.submit(aCallable).get();


Future submit(Runnable task, T result); 和上一个函数类似,区别在于前者是执行Callable对象,callable对象在执行时可以返回result,而这个函数是执行Runnable对象,返回结果是放在submit参数中的result

至此,有必要解释一下Callable和Runnable的区别。两则区别其实不大,它们的实例一般都运行在其他线程中,区别在于runnable没有返回值,而callable有,而且callable会抛出异常。

Callable的基本用法如下:

Callable<Integer> callable = new Callable<Integer>() {
  @Override
    public Integer call() throws Exception {

        Thread.sleep(3000);

        return 8;
    }
};

Future<Integer> future = mExecutor.submit(callable);

try {
    int result = future.get();
    Log.e("Junli","result is: "+ result);
} catch (Exception e) {
    e.printStackTrace();
}


Future

介绍了Callable,有必要再继续讲一下Future,Future代表了异步运算的结果,有了Future,我们就可以对异步任务进行简单地控制,比如取消,查询,获取结果等。Future类系的uml图如下所示:



Future接口的方法很明了,给出了查询,取消和 获取结果的几种方法,值得注意的是:

isDone方法在正常结束、发生异常和取消时都会返回true

cancel方法在任务已经运行完毕时,会失败,如果任务已经开始运行了,则参数mayInterruptIfRunning指定了任务是否可以被终止。

get方法在任务没有结束前会阻塞,带时间参数的版本指定了最长等待时间

Future的典型用法一般都在ExecutorServices.submit时,submit会返回一个future对象,对任务进行基本的控制。如何构建一个Future对象呢?我们先回过头来继续讲之前的ExecutorService接口。

AbstractExecutorService

ExecutorService接口定义了一些方法,开发者如果需要自己去实现,岂不是坑爹,幸好java中已经帮我们实现了一些比较典型的ExecutorService,比如常用的ThreadPoolExecutor,它会开启一个线程池,每次允许固定数量的线程运行去执行任务。ThreadPoolExecutor实际上是继承自抽象类AbstractExecutorService。为什么要有这个抽象类,因为java认为有些方法的实现不同的Executor都是相同的,可以重用。我们来看看这个抽象的父类,实现了ExecutorService接口中的那些方法呢?

其实主要有两类:

第一类是submit方法系列

AbstractExecutorService中的submit方法会根据我们传入的任务生成一个RunnableFuture对象,然后调用子类的execute方法去执行这个RunnableFuture,最后返回这个RunnableFuture, 没错从前面的uml图中我们可以看到这个RunnableFuture就是同时继承自Runnable和Future,这样,这个接口的实现类就可以同时实现任务的运行和任务的控制。

invoke系列

AbstractExecutorService已经实现了一系列的invoke函数,比如invokeAll

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
    boolean done = false;
    try {
        for (Callable<T> t : tasks) {
            RunnableFuture<T> f = newTaskFor(t);
            futures.add(f);
            execute(f);
        }
        for (int i = 0, size = futures.size(); i < size; i++) {
            Future<T> f = futures.get(i);
            if (!f.isDone()) {
                try {
                    f.get();
                } catch (CancellationException ignore) {
                } catch (ExecutionException ignore) {
                }
            }
        }
        done = true;
        return futures;
    } finally {
        if (!done)
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
    }
}


ThreadPoolExecutor

有了这些实现,子类就只需要专心实现execute和shutdown的逻辑了。而ThreadPoolExecutor正是在这两个函数上下足了功夫。关于ThreadPollExecutor执行任务的具体方式,大家可以参考我前面写的两篇关于AsyncTask的文章。

这里再将要介绍下:

先安排core_pool_size数量的任务以core thread执行

然后将多余任务入工作队列

如果队列也满,则将多余任务以非core thread运行直到,

运行的线程数量达到maximum_pool_size,则调用rejectHandler拒绝任务

ThreadPoolExecutor的构造函数提供了五个参数来控制内部的具体细节,他们是

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)


Executors工厂类

如果,用户每次在构造ThradPoolExecutor时都需要指定这五个参数,略显麻烦,java中有一个工厂类Executors,我们只需要调用其中的工厂方法就可以轻松构造出我们需要的ThreadPoolExecutor,比较常用的有:

newFixedThreadPool 用这个方法构建出的ThreadPoolExecutor,coresize与maxmumPoolSize相同,这样就将线程池的大小固定。

newCachedThreadPool 根据需要创建线程,60秒未使用的线程将会结束并从cache中移除,在大量任务时会重用之前使用过现在空闲的线程

newScheduledThreadPool 会构建一个ScheduledExecutorService,这个类我们还未讲到,它是ThreadPoolExecutor的子类,顾名思义,它能够一定的delay或 周期性的去执行任务。我们先简要介绍下这个类的使用方法:

ScheduledExecutorService

这个类在ThreadPoolExecutor的基础上提供了一系列以schedule打头的方法:

schedule

scheduleAtFixedRate

scheduleWithFixedDelay

setContinueExistingPeriodicTasksAfterShutdownPolicy 这个函数控制在executor执行了shutdown之后是否继续执行哪些本应周期性执行的任务

ScheduledExecutorService mScheduledExecutor = Executors.newScheduledThreadPool(5);

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        Log.e("Junli","task has been executed");
    }
};

mScheduledExecutor.scheduleAtFixedRate(runnable,1000,3000, TimeUnit.MILLISECONDS);


你会发现ScheduledExecutorService和Timer有类似之处,不错,他们都可以用来支持周期性任务的执行,而且ScheduleExecutorService相比TimerTask有更多的优点:

TimerTask 是基于绝对时间的,因此任务系统对时钟的改变是敏感的。而Scheduled…是基于相对时间的

所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。 而且如果一个timertask执行时间很长,超过了短任务的间隔,将会导致短任务延迟执行或者被废弃,而Scheduled会为每个任务开启一个线程则基本不会有这种问题

任何一个TimerTask的执行异常都会导致Timer终止所有任务。而Scheduled…在其中一个任务发生异常时会退出执行线程,但同时会有新的线程补充进来进行执行。

实例说明请参考: http://www.2cto.com/kf/201405/304479.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐