Android开发者必知的Java知识(四):Java并发编程
2015-04-15 10:00
441 查看
android编程中,并发编程是不可或缺的一部分,几乎所有应用程序的开发中都会用到并发编程,今天我们就来聊聊并发编程的一些事儿,我们先从java对并发编程的基本支持说起。
转载请注明出处: /article/1326066.html
.
处于UML图最顶层的是一个接口Executor,这个接口有一个方法叫做execute,Executor的这个方法可以将任务的提交和任务如何执行(包括线程的使用细节,调度等)解耦,相比直接创建线程执行任务,java更建议我们使用Executor,例如使用
java更建议我们使用
来调度任务的执行。
这种情况比较少用,更典型的情况是,我们会为每个任务新创建一个线程去执行,
ThreadPerTaskExecutor只是简单地为我们的任务分配一个线程去执行,如果我们同时需要执行1000个以上的任务,这个executor会同时开启1000+个线程去执行任务,这会给我们的机器造成比较大的开销,而且每个任务执行的效率也会很低下。因此我们需要在自己的Executor中,增加线程调度的逻辑,其中最简单地调度逻辑就是,每次开启一个线程执行一个任务,等待任务执行完毕,执行下一个任务。
每次execute一个runnable时,我们会将runnable重新包装,放入mTasks这个队列中,如何包装?就是在runnable结束之后调用scheduleNext从mTasks重取出一个新的任务并立即执行。AsyncTask中默认的执行方式就是这种。
Executor给我们规定的接口实在太少,接口ExecutorService则继承了Executor接口。
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对象可以阻塞地获得执行结果。如果想在执行任务的时候立马阻塞等待结果,可以这样做
Future submit(Runnable task, T result); 和上一个函数类似,区别在于前者是执行Callable对象,callable对象在执行时可以返回result,而这个函数是执行Runnable对象,返回结果是放在submit参数中的result
至此,有必要解释一下Callable和Runnable的区别。两则区别其实不大,它们的实例一般都运行在其他线程中,区别在于runnable没有返回值,而callable有,而且callable会抛出异常。
Callable的基本用法如下:
Future接口的方法很明了,给出了查询,取消和 获取结果的几种方法,值得注意的是:
isDone方法在正常结束、发生异常和取消时都会返回true
cancel方法在任务已经运行完毕时,会失败,如果任务已经开始运行了,则参数mayInterruptIfRunning指定了任务是否可以被终止。
get方法在任务没有结束前会阻塞,带时间参数的版本指定了最长等待时间
Future的典型用法一般都在ExecutorServices.submit时,submit会返回一个future对象,对任务进行基本的控制。如何构建一个Future对象呢?我们先回过头来继续讲之前的ExecutorService接口。
其实主要有两类:
第一类是submit方法系列
AbstractExecutorService中的submit方法会根据我们传入的任务生成一个RunnableFuture对象,然后调用子类的execute方法去执行这个RunnableFuture,最后返回这个RunnableFuture, 没错从前面的uml图中我们可以看到这个RunnableFuture就是同时继承自Runnable和Future,这样,这个接口的实现类就可以同时实现任务的运行和任务的控制。
invoke系列
AbstractExecutorService已经实现了一系列的invoke函数,比如invokeAll
这里再将要介绍下:
先安排core_pool_size数量的任务以core thread执行
然后将多余任务入工作队列
如果队列也满,则将多余任务以非core thread运行直到,
运行的线程数量达到maximum_pool_size,则调用rejectHandler拒绝任务
ThreadPoolExecutor的构造函数提供了五个参数来控制内部的具体细节,他们是
newFixedThreadPool 用这个方法构建出的ThreadPoolExecutor,coresize与maxmumPoolSize相同,这样就将线程池的大小固定。
newCachedThreadPool 根据需要创建线程,60秒未使用的线程将会结束并从cache中移除,在大量任务时会重用之前使用过现在空闲的线程
newScheduledThreadPool 会构建一个ScheduledExecutorService,这个类我们还未讲到,它是ThreadPoolExecutor的子类,顾名思义,它能够一定的delay或 周期性的去执行任务。我们先简要介绍下这个类的使用方法:
schedule
scheduleAtFixedRate
scheduleWithFixedDelay
setContinueExistingPeriodicTasksAfterShutdownPolicy 这个函数控制在executor执行了shutdown之后是否继续执行哪些本应周期性执行的任务
你会发现ScheduledExecutorService和Timer有类似之处,不错,他们都可以用来支持周期性任务的执行,而且ScheduleExecutorService相比TimerTask有更多的优点:
TimerTask 是基于绝对时间的,因此任务系统对时钟的改变是敏感的。而Scheduled…是基于相对时间的
所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。 而且如果一个timertask执行时间很长,超过了短任务的间隔,将会导致短任务延迟执行或者被废弃,而Scheduled会为每个任务开启一个线程则基本不会有这种问题
任何一个TimerTask的执行异常都会导致Timer终止所有任务。而Scheduled…在其中一个任务发生异常时会退出执行线程,但同时会有新的线程补充进来进行执行。
实例说明请参考: http://www.2cto.com/kf/201405/304479.html
转载请注明出处: /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
相关文章推荐
- Android开发者必知的Java知识(一):Java反射机制
- Android开发者必知的java知识(二)Annotation
- Android开发者必知的Java知识(三) 结合注解分析ActiveAndroid的实现
- Java基础知识强化之网络编程笔记15:Android网络通信之 Android异步任务处理(AsyncTask使用)
- Java基础知识强化之网络编程笔记16:Android网络通信之 使用Http的Get方式读取网络数据(基于HTTP通信技术)
- Java基础知识强化之网络编程笔记17:Android网络通信之 使用Http的Post方式读取网络数据(基于HTTP通信技术)
- Java基础知识强化之网络编程笔记18:Android网络通信之 使用HttpClient的Post / Get 方式读取网络数据(基于HTTP通信技术)
- Java基础知识强化之网络编程笔记19:Android网络通信之 HttpClient和传统Post、Get方式的区别
- Java基础知识强化之网络编程笔记20:Android网络通信之 Android常用OAuth登录和分享
- Java基础知识强化之网络编程笔记21:Android网络通信之 Android常用OAuth登录(获取令牌信息)
- Java基础知识强化之网络编程笔记22:Android网络通信之 Android常用OAuth登录(获取个人信息)
- Java基础知识强化之网络编程笔记23:Android网络通信之 Volley(Google开源网络通信库)
- moon java 并发的基础知识--名词解释部分 以编程思想的第21章为基础
- moon java 并发的基础知识 以编程思想的第21章为基础
- moon java 并发的基础知识 以编程思想的第21章为总体概览及问题
- Java基础知识强化之网络编程笔记24:Android网络通信之 AndroidAsync(基于nio的异步通信库)
- Java基础知识强化之网络编程笔记25:Android网络通信之 Future接口介绍(Java程序执行超时)
- [置顶] android开发之java的一些基础知识详解,java编程语法,扎实自己的android基本功
- android开发之java的一些基础知识详解,java编程语法,扎实自己的android基本功
- Java并发编程基础知识片段