Android(线程二) 线程池详解

版权声明:此文章转载自infocool

原文链接:http://www.infocool.net/kb/Android/201607/163318.html

如需转载请联系听云College团队成员小尹 邮箱:yinhy#tingyun.com

我们在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也将关闭 。

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秒打印一次。结果依次输出,相当于顺序执行各个任务,运行截图:

Android(线程二) 线程池详解

   现行大多数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个线程分别执行不同的任务,运行截图:

Android(线程二) 线程池详解

  它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为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个线程去执行,执行完后变为空闲状态,接着再执行其他等待任务,运行截图:

Android(线程二) 线程池详解

它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都处于等待状态,直到有线程空闲出来。由于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());
    }
  示例代码:
 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状态。

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

PS:

并行和并发区别

1、并行是指两者同时执行一件事,比如赛跑,两个人都在不停的往前跑;

2、并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率

QQ截图20160713173444.png

想阅读更多技术文章,请访问听云技术博客,访问听云官方网站感受更多应用性能优化魔力。

关于作者

郝淼emily

重新开始,从心开始

我要评论

评论请先登录,或注册