Android多线程编程__线程池(ThreadPoolExector)

简介: 在编程中经常会使用线程池来异步处理任务,但是每个线程池的创建和销毁都有一定的开销。

在编程中经常会使用线程池来异步处理任务,但是每个线程池的创建和销毁都有一定的开销。如果每次执行一个任务都需要开一个新线程去执行,则这些线程的创建和销毁将消耗大量的资源;并且线程都是各自为政,很难对其进行控制,更何况有一堆的线程在执行。这时就需要线程池来对线程进行管理。在java中提供了 Executor框架用于把任务的提交和执行解耦,任务的调教交给 Runnable 和 Callable,而Executor 框架用来处理任务。Executor 框架中最核心的成员就是 ThreadPoolExeeutor,它是线程池的核心实现类.

ThreadPoolExector

可以通过 ThreadPoolExector来创建一个线程池,ThreadPoolExecutor 类一共有4个构造方法,其中拥有最多参数的构造方法如下。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

corePoolSize:  核心线程数。默认情况下线程池是空的,只有任务提交时才会创建线程。如果当前运行的线程数少于 corePoolSize ,则创建新线程来处理任务;如果等于或者多于 crePoolSize ,则不再创建。如果调用线程池的 prrestartAllcoreThread 方法,线程池会提前创建并启动所有核心线程来等待任务。

maximumPoolSize: 线程池允许创建的最大线程数。如果任务队列满了并且线程数小于 maximumPoolSize时,则线程池仍旧会创建新的线程来处理任务。

keepAliveTime:  非核心线程闲置的超过时间。超过这个时间则回收。如果任务很多,并且每个任务的执行事件很短,则可以调大KeepAliveTime 来提高线程的利用率。另外,如果设置allowCoreThreadTimOut属性为 true 时,keepAliveTime 也会应用到核心线程上。

TimeOut:  keepAliveTime 参数的时间单位。可选的单位有 天(days),小时(hours),分钟(minutes),秒(seconds),毫秒(milliseconds)等。

workQueue:  任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue 类型,也就是阻塞队列。

ThreadFactory:  线程工厂。 可以用线程工厂给每个创建出来的线程设置名字。一般情况下无序设置改参数。

RejectdExecutionHandler:  饱和策略。这是当任务队列和线程池都满了时所采用的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出 RejectedExecutionException异常。此处还有3种策略,他们分别如下。

  1. CallerRunsolicy 用调用者所在的线程来处理任务。此策略提供加单的反馈控制机制,能够缓解新任务的提交速度。
  2. DiscardPolicy: 不能执行的任务,并将该任务删除

DiscardOldestPolicy: 丢弃队列最近的任务,并执行当前的任务。

线程池的处理流程和原理

  1. 提交任务后,线程池先判断线程数时候达到了核心线程数。如果未达到核心线程数,则创建核心线程处理任务;否则,就执行下一步操作。
  2. 接着线程池判断任务队列是否满了。如果没满,则将任务添加到队列中;否则,就执行下一步操作。
  3. 接着因为线程池判断满了,线程池就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就执行饱和策略,默认会抛出RejectedExecutionException异常。


  1. 如果线程池中的线程数未达到核心线程数,则创建核心线程处理任务。
  2. 如果线程数大于或者等于核心线程数,则将任务加入任务队列,线程池中的空闲线程会不断地从任务队列中取出任务进行处理。
  3. 如果任务队列满了,并且线程数没有达到最大线程数,则创建非核心线程去处理。
  4. 如果线程数超过了最大线程数,则执行饱和策略。  

线程池的种类

FixedThreadPool

FixedThreadPool是可重用固定线程数的线程。在Executors 类中提供了创建FixedThreadPool 的方法,如下所示,

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

FixedThreadPool 的 corePoolSize 和maximumPoolsize 都设置为 创建 FixedThreadPool 指定的参数Threads ,也就意味着FixedThreadPool 只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime 设置为0L,意味着多余的线程会被立即终止。因为不会产生多余的线程,所以keepAliveTime 是无效的参数。另外,任务队列采用了无界阻塞队列 LinkBlockingQueue(容量默认为Integer.MAX_VALUE)。

LinkBlockingQueue(容量默认为Integer.MAX_VALUE)。

当执行execute方法时,如果当前运行的线程未达到 corePoolSize(核心线程数)时就创建核心线程来处理任务,如果达到了核心线程数则将任务添加·到·LinkedBlockingQueue中,FixedThreadPool 就是一个有固定核心线程的线程池,并且这些线程不会被回收。当线程数超过 corePoolize 时,就将任务存储在任务队列中,当线程池有空闲线程是,则从任务队列中去任务执行。

public class MyClass {
    public static void main(String[] args) {
        //
        ExecutorService fipool= Executors.newFixedThreadPool(5);
        for (int i=0;i<10;i++){
            fipool.execute(getThread(i));
        }
        //退出线程池
        fipool.shutdown();
    }
    private static Runnable getThread(final  int i){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        };
    }
}

CachedThreadPool

CachedThreadPool 是一个根据需要创建的线程池。

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new     SynchronousQueue());
    }

CachedThreadPool 的 corePoolSize 为0,maximumPoolSize 设置为 Integer.Max_value.这意味着CachedThreadPool 没有核心线程,非核心线程是无界的。 keepAliveTime 设置为 60L,则空闲线程等待任务的最长时间为60s 。在此用了阻塞队列 SynchronousQueue ,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。

当执行 excute方法时,首先会执行 SynchrnousQueue的 offer 方法来提交任务,并且查询线程池是否有空线程执行 SynuchronousQueue 的 poll 方法移除任务。如果有子线程配对成功,将任务交给这个空闲的线程处理;当执行 excute方法时则这个空闲线程将终止。因为 maximumPoolSize 是无界的,所以如果提交的任务大于线程池中线程处理任务的速度就会不断创建新线程。另外,每次提交任务都会立即有线程去处理。所以 CachedThreadPool 比较适合于大量需要立即执行并且耗时较少的任务。

public class MyClass {
    public static void main(String[] args) {
        //
        ExecutorService fipool= Executors.newCachedThreadPool();
        for (int i=0;i<10;i++){
            cachPool.execute(getThread(i));
        }
        //退出线程池
//        cachPool.shutdown();
    }
    private static Runnable getThread(final  int i){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        };
    }
}

SingleThreadExecutor

SingThreadExecutor 是使用单个工作线程的线程池,其创建源码如下所示

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

corePoolSize 和 maxImumPoolSize 都为1,意味着 SingleThradExecutor 只有一个核心线程,其他的参数和 FixedThreadPool 一样。

阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先进先出的顺序执行任务。需要注意的是,使用完之后需要shutdown线程池,因为它与CachedThreadPool 一样,在没有任务完成时,不会释放资源。

public class MyClass {
    public static void main(String[] args) {
        //
        ExecutorService singPool= Executors.newSingleThreadExecutor();
        for (int i=0;i<10;i++){
            singPool.execute(getThread(i));
        }
        //退出线程池
        singPool.shutdown();
    }
    private static Runnable getThread(final  int i){
        return new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        };
    }
}

ScheduledThreadPool

ScheduledThreadPool 是一个能实现定时和周期性任务的线程池,

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

这里创建了 ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor继承自 自ThreadPoolExecutor,它主要用于给定延时之后的任务或者定期处理任务。

ScheduledThreadPoolExecutor 构造方法如下

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

最终调用的是 ThreadPoolExecutor 的构造方法。corePoolSize 是传入的固定值,maximumPoolSize的值是 Integer.MAX_VALUE .因为采用的 DelayedWorkQueue是无界的,所以maximumPoolSize这个参数是无效的。它的执行过程如图

当执行 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate 或者 scheduleWithFixedDelay方法时,会向DelayedWorkQueue 添加一个 实现 RunnableScheduledFuture 接口的ScheduledFutureTask(任务的包装类),并会检查运行的线程是否达到 corePoolSize。如果没有则新建线程并启动它,但并不是立即去执行任务,而是去 DelayedWorkQueue 中取ScheduledFutureTask,然后去执行任务。如果运行的线程达到了corePoolSize时,则将任务添加到DelayedWorkQueue中。DelayedWorkQueue会将任务进行排序,先要执行的任务放在队列的前面。其跟此前介绍的线程池不同的是,当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中

public class MyClass {
    public static void main(String[] args) {
        //
        ScheduledExecutorService singPool= Executors.newScheduledThreadPool(10);
        singPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始");
            }
        },3,3, TimeUnit.SECONDS);
    }
}

scheduleAtFixedRate:以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。

scheduleWithFixedDelay是以上一个任务结束时开始计时,period时间过去后,立即执行。

目录
相关文章
|
11天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
8天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
11天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
11天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
39 1
|
15天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
16天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
42 4
|
16天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
44 3
|
7天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
9天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
11天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。