Android(线程二) 线程池详解

原创 2016年07月11日 22:55:03

  我们在ListView中需要下载资源时,赞不考虑缓存机制,那么每一个Item可能都需要开启一个线程去下载资源(如果没有线程池),如果Item很多,那么我们可能就会无限制的一直创建新的线程去执行下载任务,最终结果可能导致,应用卡顿、手机反应迟钝!最坏的结果是,用户直接卸载掉该App。所以,我们在实际开发中需要考虑多线程,多线程就离不开线程池。如果你对线程还不了解,可以看看这篇文章,Android(线程一) 线程
  使用线程池的优点:
(1).重用线程,避免线程的创建和销毁带来的性能开销;
(2).能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象;
(3).能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
  Android中的线程池是来自Java。那么就需要看看Java中的线程池。Java中的线程池有四种:
(1).SingleThreadExecutor,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行;
(2).CachedThreadPool,创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;
(3).FixedThreadPool,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;
(4).ScheduledThreadPool,创建一个定长线程池,支持定时及周期性任务执行。
PS:创建线程池,使用Executors类。通过这个类能够获得多种线程池的实例。Executors的核心构造方法ThreadPoolExecutor(),接下来,我们看看该方法说明,

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

  参数说明:

   corePoolSize 线程池维护线程的最少数量(核心线程数量),默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超市策略,这个时间间隔由keepAliveTime所指定,当等待时间超过过keepAliveTime所指定的时长后,核心线程就会被终止。

   maximumPoolSize  线程池维护线程的最大数量,当活动线程达到这个数值后,后续的新人无语将会被阻塞。

   keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间。

   unit keepAliveTime时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
   workQueue 线程池所使用的缓冲队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
   threadFactory 新建线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r).
   handler 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理;
   其中corePoolSize,maximumPoolSize,workQueue之间关系,如下描述, 
(1).当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;
(2).当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 ;
(3).当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务 ;
(4).当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 ;
(5).当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程 ;

(6).当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭 。

PS:在ThreadPoolExecutor类中有几个非常重要的方法:
1.execute()
实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
2.submit()
在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
3.shutdown()  
线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
4.shutdownNow()  
线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

   Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法表示任务是否已经完成,若任务完成,则返回true;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
  也就是说Future提供了三种功能:
  1)判断任务是否完成;
  2)能够中断任务;
  3)能够获取任务执行结果。

1.SingleThreadExecutor。
ExecutorService pool = Executors.newSingleThreadExecutor();
  创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。接着看看newSingleThreadExecutor源码实现,
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行 。示例代码:

public static void singThreadExecutors() {
		ExecutorService pool = Executors.newSingleThreadExecutor();
		// 创建实现了runnable接口的对象
		Thread t1 = new MyThread();
		Thread t2 = new MyThread();
		Thread t3 = new MyThread();
		Thread t4 = new MyThread();
		Thread t5 = new MyThread();
		// 将线程放入池中进行执行
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		// 关闭线程池
		pool.shutdown();
	}
class MyThread extends Thread {
	@Override
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName()
					+ " is running...");
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

每隔2秒打印一次。结果依次输出,相当于顺序执行各个任务,运行截图:


   现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

   这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。

2.CachedThreadPool。

ExecutorService pool = Executors.newCachedThreadPool();
   创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。接着看看newCachedThreadPool源码实现,
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
核心线程为0,最大线程数为231-1,线程keepAliveTime为60,单位为秒,缓存队列为SynchronousQueue的线程池,因为多个线程共用一个SynchronousQueue,所以必须保证各个线程对SynchronousQueue的访问是串行的,也就是前一个线程没有使用完,后一个线程就必须等待,以保证SynchronousQueue内部数据的完整性。示例代码:
public static void cachedThreadPool() {
		ExecutorService pool = Executors.newCachedThreadPool();
		// 创建实现了runnable接口的对象
		Thread t1 = new MyThread();
		Thread t2 = new MyThread();
		Thread t3 = new MyThread();
		Thread t4 = new MyThread();
		Thread t5 = new MyThread();
		// 将线程放入池中进行执行
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		// 关闭线程池
		pool.shutdown();
	}

创建了5个线程分别执行不同的任务,运行截图:

  它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为231-1。由于231-1是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置的线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会被立即执行,因为在这种场景下SynchronousQueue是无法插入任务的。这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的。

3.FixedThreadPool。
ExecutorService pool = Executors.newFixedThreadPool(int nThreads);
创建一个可重用固定线程数的线程池,nThreads=3表示创建了3个线程的线程池(定长线程池的大小最好根据系统资源进行设置),接着看看newFixedThreadPool源码实现,
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
corePoolSize和maximumPoolSize的大小是一样的,都是根据传递的参数设置的,keepAliveTime为0,表示不想keep alive。示例代码:
public static void fixedThreadPool() {
		ExecutorService pool = Executors.newFixedThreadPool(3);
		// 创建实现了runnable接口的对象
		Thread t1 = new MyThread();
		Thread t2 = new MyThread();
		Thread t3 = new MyThread();
		Thread t4 = new MyThread();
		Thread t5 = new MyThread();
		// 将线程放入池中进行执行
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		// 关闭线程池
		pool.shutdown();
	}

线程池的线程数量固定为3,首先创建3个线程去执行,执行完后变为空闲状态,接着再执行其他等待任务,运行截图:

   它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都处于等待状态,直到有线程空闲出来。由于FixedThreadPool 只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求。

4.ScheduledThreadPool。

ScheduledExecutorService pool = Executors.newScheduledThreadPool(int corePoolSize)
接着看看newScheduledThreadPool的源码,
 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

 super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue())的源码如下,依然是调用ThreadPoolExecutor去创建线程池,

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
   每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

  示例代码:

 ScheduledExecutorService scheduledExecutorService=Executors.newScheduledThreadPool(4);
 //2000ms 后执行command
 scheduledExecutorService.schedule(command,2000,TimeUnit.MILLISECONDS);
 //延迟10ms后,每隔 1000ms执行一次command
 scheduledExecutorService.scheduleAtFixedRate(command,10,1000,TimeUnit.MILLISECONDS);

   它的核心线程数据是固定的,而非核心线程数是没有限制的,并且当非核心线程限制时会被立即回收。ScheduledThreadPool这类线程池主要用于执行定时任务和具有固定周期的重复任务。

总结:

(1).最大线程数一般设为2N+1最好,N是CPU核数;

(2).看过AsyncTask源码的同学就会知道,其实它的内部也是一个线程池;

(3).当创建线程池后,初始时,线程池处于RUNNING状态;
(4).如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
(5).如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
(6).当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

   线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

   我们都知道Android系统提供了一个封装了线程和异步消息处理的类,AsyncTask即异步任务,该类中就涉及到线程池,详情请看,Android 源码解析AsyncTask(一)Android 源码解析AsyncTask(二)

PS:并行和并发区别

1、并行是指两者同时执行一件事,比如赛跑,两个人都在不停的往前跑;
2、并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率

有关并发和并行更加详细的描述,请看这篇文章, 并发和并行浅谈

参考资料: 《Android开发艺术探索》。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

android 创建多线程的几种方法

原文地址:http://www.cnblogs.com/gw811/archive/2012/10/15/2724882.html

Android HandlerThread 完全解析

转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47079737; 本文出自:【张鸿洋的博客】 1、概述话说最近股市变...

Android实现双进程守护

如何保证Service不被Kill有关Service的知识请参考Android Service全面解析这篇文章,写的很详细。(1)onStartCommand方法,返回START_STICKY@Ove...

Android开发之如何保证Service不被杀掉(broadcast+system/app)

最近项目要实现这样一个效果:运行后,要有一个service始终保持在后台运行,不管用户作出什么操作,都要保证service不被kill,这可真是一个难题。参考了现今各种定制版的系统和安全厂商牛虻软件,...

Android - 线程同步

什么是线程同步?     当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。 实现同步机制有两个方...

java - 深入篇 --Java的多线程实现

今天我们要探究的是一个老司机开车的问题--多线程,关于这个问题,整个体系很是复杂,同时也是面试中必考的一个考点,最重要的是,如果没有掌握到这个知识点,那么你在接下来的学习中,会觉得非常的痛苦。所以,在...

关于前端HTML你需要知道的一切

本文讲解了关于HTML绝大部分的标签的使用,可以很好地对HTML有个比较全面的了解。还有一些HTML5的若干标签。最后讲了在HTML中引用其他文件 关于路径问题(相对路径和绝对路径)

C#版的My物件(新书连载)

C#版的My物件(新书连载)My物件是VB.NET下的物件,所以C#不能使用My物件,所有专家都是这么说的,但祭司要推翻它,祭司要教你威力无上的秘密咒术,彻底破解My物件在C#环境下的使用方式! 节录...

android线程

android线程:通用线程方案:1、Handler监听者方式2、Handler钩子方式3、AsyncTask框架Activity的UI线程方案:1、runOnUiThread(Runable)Vie...

Android5.0以上app进程保活的正确姿势

有图有真相, 使用有情怀的锤子手机验证杀掉进程后能自启, 我设置的时间间隔为500ms(仅仅是为了测试)。                  ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)