java-Exectors提供的四种基本线程池用法与比较详解

来源:互联网 发布:cygwin 运行linux程序 编辑:程序博客网 时间:2024/06/01 19:08

一、概述

我们的任务提交到线程池之后又是按照什么样的规则去运行呢?OK,它们遵循如下规则:

1.execute一个线程之后,如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行

2.execute一个线程之后,如果线程池中的线程数已经达到核心线程数,且workQueue未满,则将新线程放入workQueue中等待执行

3.execute一个线程之后,如果线程池中的线程数已经达到核心线程数但未超过非核心线程数,且workQueue已满,则开启一个非核心线程来执行任务

4.execute一个线程之后,如果线程池中的线程数已经超过非核心线程数,则按照Hanlder策略做对应的方案,拒绝、交给调用线程处理等。


OK,基于以上讲解,我们来看一个Demo:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.         poolExecutor = new ThreadPoolExecutor(35,  
  6.                 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));  
  7. }  
  8.   
  9.     public void btnClick(View view) {  
  10.         for (int i = 0; i < 30; i++) {  
  11.             final int finalI = i;  
  12.             Runnable runnable = new Runnable() {  
  13.                 @Override  
  14.                 public void run() {  
  15.                     SystemClock.sleep(2000);  
  16.                     Log.d("google_lenve_fb""run: " + finalI);  
  17.                 }  
  18.             };  
  19.             poolExecutor.execute(runnable);  
  20.         }  
  21.     }  

执行结果如下:

OK,由于核心线程数为3,workQueue的大小为128,所以我们的线程的执行应该是先启动三个核心线程来执行任务,剩余的27个任务全部存在workQueue中,等待核心线程空余出来之后执行。OK,那我把构造ThreadPoolExecutor的参数修改一下,来验证一下我们上面的结论正确与否:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. poolExecutor = new ThreadPoolExecutor(330,  
  2.                 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(6));  

如上,我把最大线程数改为30,而把线程队列大小改为6(实际开发中 不会这样来设置,这里只是为了验证结论),我们来看看执行结果:

OK,首先打印出来0,1,2说明往核心线程添加了三个任务,然后将3,4,5,6,7,8六个任务添加到了任务队列中,接下来要添加的任务因为核心线程已满,队列已满所以就直接开一个非核心线程来执行,因此后添加的任务反而会先执行(3,4,5,6,7,8都在队列中等着),所以我们看到的打印结果是先是0~2,然后9~29,然后3~8,当然,我们在实际开发中不会这样来配置最大线程数和线程队列。那如果我们需要自己来配置这些参数,该如何配置呢?参考一下AsyncTask,AsyncTask部分源码如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public abstract class AsyncTask<Params, Progress, Result> {  
  2.     private static final String LOG_TAG = "AsyncTask";  
  3.   
  4.     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();  
  5.     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;  
  6.     private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;  
  7.     private static final int KEEP_ALIVE = 1;  
  8.   
  9.     private static final ThreadFactory sThreadFactory = new ThreadFactory() {  
  10.         private final AtomicInteger mCount = new AtomicInteger(1);  
  11.   
  12.         public Thread newThread(Runnable r) {  
  13.             return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());  
  14.         }  
  15.     };  
  16.   
  17.     private static final BlockingQueue<Runnable> sPoolWorkQueue =  
  18.             new LinkedBlockingQueue<Runnable>(128);  
  19.   
  20.     /** 
  21.      * An {@link Executor} that can be used to execute tasks in parallel. 
  22.      */  
  23.     public static final Executor THREAD_POOL_EXECUTOR  
  24.             = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
  25.                     TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);  
  26.         ....  
  27.         ....  
  28. }  

我们看到,核心线程数为手机CPU数量+1(cpu数量获取方式Runtime.getRuntime().availableProcessors()),最大线程数为手机CPU数量×2+1,线程队列的大小为128,OK,那么小伙伴们在以后使用线程池的过程中可以参考这个再结合自己的实际情况来配置参数。

OK,那么和线程池有关的最基本的ThreadPoolExecutor我们就说完了,接下来我们就来看看系统配置好的提供给我们的线程池。

2、FixedThreadPool

FixedThreadPool是一个核心线程数量固定的线程池,创建方式如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  

源码如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static ExecutorService newFixedThreadPool(int nThreads) {  
  2.         return new ThreadPoolExecutor(nThreads, nThreads,  
  3.                                       0L, TimeUnit.MILLISECONDS,  
  4.                                       new LinkedBlockingQueue<Runnable>());  
  5.     }  

我们看到核心线程数和最大线程数一样,说明在FixedThreadPool中没有非核心线程,所有的线程都是核心线程,且线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被销毁(这样可让FixedThreadPool更快速的响应请求),最后的线程队列是一个LinkedBlockingQueue,但是LinkedBlockingQueue却没有参数,这说明线程队列的大小为Integer.MAX_VALUE(2的31次方减1),OK,看完参数,我们大概也就知道了FixedThreadPool的工作特点了,当所有的核心线程都在执行任务的时候,新的任务只能进入线程队列中进行等待,直到有线程被空闲出来。OK,我们来看一个Demo:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  2.         for (int i = 0; i < 30; i++) {  
  3.             final int finalI = i;  
  4.             Runnable runnable = new Runnable(){  
  5.                 @Override  
  6.                 public void run() {  
  7.                     SystemClock.sleep(3000);  
  8.                     Log.d("google_lenve_fb""run: "+ finalI);  
  9.                 }  
  10.             };  
  11.             fixedThreadPool.execute(runnable);  
  12.         }  

执行结果如下:

这执行结果也和我们想的一致,先往核心线程中添加三个任务,剩余任务进入到workQueue中等待,当有空闲的核心线程时就执行任务队列中的任务。

3、SingleThreadExecutor

singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1,如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static ExecutorService newSingleThreadExecutor() {  
  2.     return new FinalizableDelegatedExecutorService  
  3.         (new ThreadPoolExecutor(11,  
  4.                                 0L, TimeUnit.MILLISECONDS,  
  5.                                 new LinkedBlockingQueue<Runnable>()));  
  6. }  

使用SingleThreadExecutor的一个最大好处就是可以避免我们去处理线程同步问题,其实如果我们把FixedThreadPool的参数传个1,效果不就和SingleThreadExecutor一致了,来看个Demo:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  
  2.         for (int i = 0; i < 30; i++) {  
  3.             final int finalI = i;  
  4.             Runnable runnable = new Runnable() {  
  5.                 @Override  
  6.                 public void run() {  
  7.                     Log.d("google_lenve_fb""run: " + Thread.currentThread().getName() + "-----" + finalI);  
  8.                     SystemClock.sleep(1000);  
  9.                 }  
  10.             };  
  11.             singleThreadExecutor.execute(runnable);  
  12.         }  

执行效果如下:

.

4、CachedThreadPool

CachedTreadPool一个最大的优势是它可以根据程序的运行情况自动来调整线程池中的线程数量,CachedThreadPool源码如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static ExecutorService newCachedThreadPool() {  
  2.     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
  3.                                   60L, TimeUnit.SECONDS,  
  4.                                   new SynchronousQueue<Runnable>());  
  5. }  

我们看到,CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,另外,它是有线程超时机制的,超时时间为60秒,这里它使用了SynchronousQueue作为线程队列,SynchronousQueue的特点上文已经说过了,这里不再赘述。那么我们提交到CachedThreadPool消息队列中的任务在执行的过程中有什么特点呢?由于最大线程数为无限大,所以每当我们添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务,如果没有空闲线程,则创建新线程来执行任务。根据CachedThreadPool的特点,我们可以在有大量任务请求的时候使用CachedThreadPool,因为当CachedThreadPool中没有新任务的时候,它里边所有的线程都会因为超时而被终止。OK,我们来看一个Demo:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  2.         for (int i = 0; i < 30; i++) {  
  3.             final int finalI = i;  
  4.             Runnable runnable = new Runnable(){  
  5.                 @Override  
  6.                 public void run() {  
  7.                     Log.d("google_lenve_fb""run: " + Thread.currentThread().getName() + "----" + finalI);  
  8.                 }  
  9.             };  
  10.             cachedThreadPool.execute(runnable);  
  11.             SystemClock.sleep(2000);  
  12.         }  

每次添加完任务之后我都停两秒在添加新任务,由于这里的任务执行不费时,我们可以猜测这里所有的任务都使用同一个线程来执行(因为每次添加新任务的时候都有空闲的线程),运行结果如下:

和我们的想法基本一致。OK,那如果我把代码稍微改一下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  2.         for (int i = 0; i < 30; i++) {  
  3.             final int finalI = i;  
  4.             Runnable runnable = new Runnable(){  
  5.                 @Override  
  6.                 public void run() {  
  7.                     SystemClock.sleep(2000);  
  8.                     Log.d("google_lenve_fb""run: " + Thread.currentThread().getName() + "----" + finalI);  
  9.                 }  
  10.             };  
  11.             cachedThreadPool.execute(runnable);  
  12.             SystemClock.sleep(1000);  
  13.         }  

每个任务在执行的过程中都先休眠两秒,但是我向线程池添加任务则是每隔1s添加一个任务,这样的话,添加第一个任务时先开新线程,添加第二个任务时,由于第一个新线程尚未执行完,所以又开一个新线程,添加第三个任务时,第一个线程已经空闲下来了,直接第一个线程来执行第三个任务,依此类推。我们来看看运行结果:

5.ScheduledThreadPool

ScheduledThreadPool是一个具有定时定期执行任务功能的线程池,源码如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ScheduledThreadPoolExecutor(int corePoolSize) {  
  2.     super(corePoolSize, Integer.MAX_VALUE,  
  3.           DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,  
  4.           new DelayedWorkQueue());  
  5. }  

我们可以看到,它的核心线程数量是固定的(我们在构造的时候传入的),但是非核心线程是无穷大,当非核心线程闲置时,则会被立即回收。

使用ScheduledThreadPool时,我们可以通过如下几个方法来添加任务:

1.延迟启动任务:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ScheduledFuture<?> schedule(Runnable command,  
  2.                                    long delay, TimeUnit unit);  
示例代码:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  
  2.             Runnable runnable = new Runnable(){  
  3.                 @Override  
  4.                 public void run() {  
  5.                     Log.d("google_lenve_fb""run: ----");  
  6.                 }  
  7.             };  
  8.         scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);  

2.延迟定时执行任务:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,  
  2.                                               long initialDelay,  
  3.                                               long period,  
  4.                                               TimeUnit unit);  

延迟initialDelay秒后每个period秒执行一次任务。示例代码:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  
  2.             Runnable runnable = new Runnable(){  
  3.                 @Override  
  4.                 public void run() {  
  5.                     Log.d("google_lenve_fb""run: ----");  
  6.                 }  
  7.             };  
  8.         scheduledExecutorService.scheduleAtFixedRate(runnable, 11, TimeUnit.SECONDS);  

延迟1秒之后每隔1秒执行一次新任务。

3.延迟执行任务

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,  
  2.                                                  long initialDelay,  
  3.                                                  long delay,  
  4.                                                  TimeUnit unit);  

第一次延迟initialDelay秒,以后每次延迟delay秒执行一个任务。示例代码:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);  
  2.             Runnable runnable = new Runnable(){  
  3.                 @Override  
  4.                 public void run() {  
  5.                     Log.d("google_lenve_fb""run: ----");  
  6.                 }  
  7.             };  
  8.         scheduledExecutorService.scheduleWithFixedDelay(runnable, 11, TimeUnit.SECONDS);  

第一次延迟1秒之后,以后每次也延迟1秒执行。


OK,至此,Android开发中常用的线程池就说完了。

6.线程池其他常用功能

1.shutDown()  关闭线程池,不影响已经提交的任务

2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程

3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收

4.submit 一般情况下我们使用execute来提交任务,但是有时候可能也会用到submit,使用submit的好处是submit有返回值,举个栗子:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void submit(View view) {  
  2.     List<Future<String>> futures = new ArrayList<>();  
  3.     ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(351,  
  4.             TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());  
  5.     for (int i = 0; i < 10; i++) {  
  6.         Future<String> taskFuture = threadPoolExecutor.submit(new MyTask(i));  
  7.         //将每一个任务的执行结果保存起来  
  8.         futures.add(taskFuture);  
  9.     }  
  10.     try {  
  11.         //遍历所有任务的执行结果  
  12.         for (Future<String> future : futures) {  
  13.             Log.d("google_lenve_fb""submit: " + future.get());  
  14.         }  
  15.     } catch (InterruptedException e) {  
  16.         e.printStackTrace();  
  17.     } catch (ExecutionException e) {  
  18.         e.printStackTrace();  
  19.     }  
  20. }  
  21.   
  22. class MyTask implements Callable<String> {  
  23.   
  24.     private int taskId;  
  25.   
  26.     public MyTask(int taskId) {  
  27.         this.taskId = taskId;  
  28.     }  
  29.   
  30.     @Override  
  31.     public String call() throws Exception {  
  32.         SystemClock.sleep(1000);  
  33.         //返回每一个任务的执行结果  
  34.         return "call()方法被调用----" + Thread.currentThread().getName() + "-------" + taskId;  
  35.     }  
  36. }  

使用submit时我们可以通过实现Callable接口来实现异步任务。在call方法中执行异步任务,返回值即为该任务的返回值。Future是返回结果,返回它的isDone属性表示异步任务执行成功!

5. 自定义线程池

除了使用submit来定义线程池获取线程执行结果之外,我们也可以通过自定义ThreadPoolExecutor来实现这个功能,如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void customThreadPool(View view) {  
  2.     final MyThreadPool myThreadPool = new MyThreadPool(351, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>());  
  3.     for (int i = 0; i < 10; i++) {  
  4.         final int finalI = i;  
  5.         Runnable runnable = new Runnable(){  
  6.             @Override  
  7.             public void run() {  
  8.                 SystemClock.sleep(100);  
  9.                 Log.d("google_lenve_fb""run: " + finalI);  
  10.             }  
  11.         };  
  12.         myThreadPool.execute(runnable);  
  13.     }  
  14. }  
  15. class MyThreadPool extends ThreadPoolExecutor{  
  16.   
  17.     public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {  
  18.         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);  
  19.     }  
  20.   
  21.     @Override  
  22.     protected void beforeExecute(Thread t, Runnable r) {  
  23.         super.beforeExecute(t, r);  
  24.         Log.d("google_lenve_fb""beforeExecute: 开始执行任务!");  
  25.     }  
  26.   
  27.     @Override  
  28.     protected void afterExecute(Runnable r, Throwable t) {  
  29.         super.afterExecute(r, t);  
  30.         Log.d("google_lenve_fb""beforeExecute: 任务执行结束!");  
  31.     }  
  32.   
  33.     @Override  
  34.     protected void terminated() {  
  35.         super.terminated();  
  36.         //当调用shutDown()或者shutDownNow()时会触发该方法  
  37.         Log.d("google_lenve_fb""terminated: 线程池关闭!");  
  38.     }  
  39. }  

执行结果如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. D/google_lenve_fb: beforeExecute: 开始执行任务!  
  2. D/google_lenve_fb: run: 0  
  3. D/google_lenve_fb: beforeExecute: 任务执行结束!  
  4. D/google_lenve_fb: beforeExecute: 开始执行任务!  
  5. D/google_lenve_fb: run: 1  
  6. D/google_lenve_fb: beforeExecute: 任务执行结束!  
  7. D/google_lenve_fb: beforeExecute: 开始执行任务!  
  8. D/google_lenve_fb: run: 2  
  9. D/google_lenve_fb: beforeExecute: 任务执行结束!  

OK,以上就是关于线程池的使用总结。。


参考资料

1.http://blog.csdn.NET/u010687392/article/details/49850803

2.《Android开发艺术探索》 


个人笔记

线程池种类workQueue   newFiexedThreadPoolLinkedBlockingQueue不释放线程线程数固定 newCachedThreadPoolSynchronousQueue释放线程线程数无数大 newSingleThreadExecutorLinkedBlockingQueue 一个线程相比自己创建单个线程要稳定newScheduledThreadPool              



原创粉丝点击