Android的线程和线程池

读完开发艺术探讨做的一些总结。

介绍:
在操作系统中,线程是操作系统调度最小的单元,同时线程又是一种受限的资源系统,既线程不可能无限制的生产,而且线程的创建和销毁都会有相应的开销。

在android开发中,从用途上来说,android 主要分为主线程和子线程,主线程负责处理和界面相关的事情,子线程往往处理耗时的操作。(在java中默认情况下,一个进程只有一个线程,这个线程就叫主线程,主线程在任何时候都必须有较高的响应速度,否则就会产生一种界面卡顿的感觉。如果在主线程中执行耗时操作那么容易导致程序无法及时响应,这个时候子线程就派上用处,子线程也叫工作线程,除了主线程意外的都是子线程)

通俗的理解就是一个公司一般只会有一位老板,老板多了做事情容易乱套,每个子线程都是老板手下的业务人员,老板一般只处理大事件,负责一些活动的签字行为等,当事情的具体细节老板就会交给他手下的员工去处理,这样就不会影响老板谈其它的生意发家致富了。

在android中除了Thread外,系统本身也有许多扮演子线程的角色,比如:

AsyncTask,IntentService等,不过这些本质上依然是传统的线程。

Android线程池
优点:
1.重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。

2.有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。

3.能够对线程进行简单的管理,并提供定时执行及指定间隔循环执行等功能。

Android中的线程池来源与java中的Executor,真正实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数,通过不同的参数可以创建功效不同的线程池。接下来先介绍一下ThreadPoolExecutor。

ThreadPoolExecutor
首先看一下ThreadPoolExecutor比较常用的一个构造方法:


   
corePoolSize (线程的核心数,默认情况下核心线程会一直在线程
      中存活,跟allowCoreThreadTimeOut有关)
 maximumPoolSize (线程池能够容纳的最大线程数,当活动线程
     数大道这个数值后,后续的新任务将会被阻塞)
      keepAliveTime (非核心线程闲置的超时时长,超过后非核
心线程会被回收,allowCoreThreadTimeOut为true时,对核心线程    
           也有效)
      unit          (枚举单位,代表keepAliveTime的时间单位)
      workQueue     (线程池中的任务列队,通过线程池的execute
方法提交的Runnable对象会存储在这个参数中)
      threadFactory  (线程工厂,为线程池提供创建新线程的功能
threadFactory是一个接口,它只有一个方法:ThreadnewThread(Runnable r))
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)

ThreadPoolExecutor执行任务时候的大致规则:
1.如果线程池中的线程数量没有大道核心线程的数量,那么会直接启动一个核心线程来执行任务。
2.如果线程池中的线程数量已经达到或者超过核心线程数量,那么任务会被插入到任务列队中排队等待执行。
3.如果步骤2中无法将任务插入到任务列队中,这往往是由于任务列队已满,这个时候如果线程数量没有达到线程池中规定的最大值,那么会立刻启动一个非核心线程来执行任务。
4.如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionExecutionHandler的rejectedExecution方法来通知调用者。(参数不常用)
(allowCoreThreadTimeOut方法只有在ThreadPoolExecutor中实现了)
之前说到过的AsyncTask的源码中就有ThreadPoolExecutor的具体实现,可以看一下,接下来自己简单实现一个线程池:

 private void initThreadPoolExecutor(){
        int corePool = 5;
        int maxPool = 10;
        long keppTime = 10000;
        //BlockingQueue是一个接口
        BlockingDeque<Runnable> poolQueue = new LinkedBlockingDeque<>(100);
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(@NonNull Runnable r) {
                return null;
            }
        };
        Executor executor = new ThreadPoolExecutor(corePool,maxPool,keppTime, TimeUnit.MILLISECONDS,poolQueue,threadFactory);
    }

每个参数之前的备注中有,应该很好理解其中的意思。

在Android中常用的线程池有4种,它们都直接或者间接地通过配置ThreadPoolExecutor来实现自己的功能特性的,
在Executors中有很多工厂方法,可以直接实例出来这些对象。

1.FixedThreadPool
通过Executors的newFixedThreadPool方法来创建,我们首页来看看他的实现方法:

  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

一般情况下,可以看到只用传入一个线程的具体数量就可以了,在其内部中,核心线程和最大线程是相同的,即代表只有核心线程,这意味着它能够更加快速的响应外界的请求。而且超时为0代表当线程为控件状态时,它们不会被回收,除非线程池被关闭。实例化的LinkedBlockingQueue长度不限,则代表新任务都会处于等待状态,直到有线程空闲出来。其实就跟字面意思一样,这是一个固定线程数的线程池。

下面介绍一下简单的使用方法:

 Runnable runnable = new Runnable() {
            @Override
            public void run() {
                
            }
        };
        ExecutorService fixed = Executors.newFixedThreadPool(5);
        fixed.execute(runnable);

2.CachedThreadPool

  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }


    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

通过Executors的newExecutorService 方法来创建,由上可以看到,它没有核心线程,只有不限制的非核心线程,而且有超时机制,当超过60秒钟后,线程就会被回收。由此可以看出只要有任务进来时,就会有线程去执行,当任务完成一段时间后,线程就会被回收,当闲置状态的时候,线程池中实际上是没有任务线程的,几乎不占用任何系统资源。这类线程比较适合执行大量的耗时较少的任务。
简单使用方式:

  Runnable runnable = new Runnable() {
            @Override
            public void run() {

            }
        };
        ExecutorService cache = Executors.newCachedThreadPool();
        cache.execute(runnable);

3.ScheduledThreadPoolExecutor

   Runnable runnable = new Runnable() {
            @Override
            public void run() {

            }
        };

 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

通过Executors的newScheduledThreadPool方法来创建,核心线程固定,最大线程数量没有限制,当非核心线程闲置时会被立刻回收,其中queue的类型是一个DelayedWorkQueue(),则代表这是一个用来执行延时任务,或者重复周期性的任务。
简单使用方法:

ScheduledExecutorService schedule = Executors.newScheduledThreadPool(3);
        //1000毫秒之后执行
        schedule.schedule(runnable,1000,TimeUnit.MILLISECONDS);
        //1000毫秒后执行,每隔10000毫秒执行一次
        schedule.scheduleAtFixedRate(runnable,1000,10000,TimeUnit.MILLISECONDS);

4.newSingleThreadExecutor

  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

这个线程内部只有一个核心线程,这么做可以确保所有的任务都在一个线程中按顺序执行,这样统一所有的外界任务到一个线程中,使得在这些任务之间不需要处理线程同步的问题。
简单使用方式:

 Runnable runnable = new Runnable() {
           @Override
           public void run() {

           }
       };
       ExecutorService signle = Executors.newSingleThreadExecutor();
       signle.execute(runnable);

其中当线程不使用的时候可以用shutdown()方法,来关闭使用。

推荐阅读更多精彩内容