多线程2,线程池深入理解

简介:

目录介绍

  • 1.ThreadPoolExecutor类介绍
  • 1.1 构造函数
  • 1.2 参数解析
  • 1.3 遵循的规则
  • 1.4 使用线程池管理线程的优点
  • 2.关于线程池的分类
  • 2.1 FixedThreadPool
  • 2.2 CachedThreadPool
  • 2.3 ScheduledThreadPool
  • 2.4 SingleThreadExecutor
  • 3.线程池一般用法
  • 3.1 一般方法介绍
  • 3.2 newFixedThreadPool的使用
  • 3.3 newSingleThreadExecutor的使用
  • 3.4 newCachedThreadPool的使用
  • 3.5 newScheduledThreadPool的使用
  • 3.6 线程创建规则
  • 4.线程池封装
  • 4.1 具体可以参考下篇文章
  • 4.2 参考博客

前言介绍

1.ThreadPoolExecutor类介绍

1.1 构造函数

  • ExecutorService是最初的线程池接口,ThreadPoolExecutor类是对线程池的具体实现,它通过构造方法来配置线程池的参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1.2 参数解析

  • corePoolSize,线程池中核心线程的数量,默认情况下,即使核心线程没有任务在执行它也存在的,我们固定一定数量的核心线程且它一直存活这样就避免了一般情况下CPU创建和销毁线程带来的开销。我们如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程就会有超时策略,这个时间由keepAliveTime来设定,即keepAliveTime时间内如果核心线程没有回应则该线程就会被终止。allowCoreThreadTimeOut默认为false,核心线程没有超时时间。
  • maximumPoolSize,线程池中的最大线程数,当任务数量超过最大线程数时其它任务可能就会被阻塞。最大线程数=核心线程+非核心线程。非核心线程只有当核心线程不够用且线程池有空余时才会被创建,执行完任务后非核心线程会被销毁。
  • keepAliveTime,非核心线程的超时时长,当执行时间超过这个时间时,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,此属性也作用在核心线程上。
  • unit,枚举时间单位,TimeUnit。
  • workQueue,线程池中的任务队列,我们提交给线程池的runnable会被存储在这个对象上。

1.3 遵循的规则

  • 当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
  • 如果线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行;
  • 如果在上一步骤中任务队列已满但是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务;
  • 如果上一步骤中线程数量达到了限定线程总量,那么线程池则拒绝执行该任务,且ThreadPoolExecutor会调用RejectedtionHandler的rejectedExecution方法来通知调用者。

1.4 使用线程池管理线程的优点

  • 1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
  • 2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量
  • 3、在执行大量异步任务时提高了性能
  • 4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

2.关于线程池的分类

2.1 FixedThreadPool

  • 通过Executors的newFixedThreadPool()方法创建,它是个线程数量固定的线程池,该线程池的线程全部为核心线程,它们没有超时机制且排队任务队列无限制,因为全都是核心线程,所以响应较快,且不用担心线程会被回收。

2.2 CachedThreadPool

  • 通过Executors的newCachedThreadPool()方法来创建,它是一个数量无限多的线程池,它所有的线程都是非核心线程,当有新任务来时如果没有空闲的线程则直接创建新的线程不会去排队而直接执行,并且超时时间都是60s,所以此线程池适合执行大量耗时小的任务。由于设置了超时时间为60s,所以当线程空闲一定时间时就会被系统回收,所以理论上该线程池不会有占用系统资源的无用线程。

2.3 ScheduledThreadPool

  • 通过Executors的newScheduledThreadPool()方法来创建,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,但是它的非核心线程超时时间是0s,所以非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

2.4 SingleThreadExecutor

  • 通过Executors的newSingleThreadExecutor()方法来创建,它内部只有一个核心线程,它确保所有任务进来都要排队按顺序执行。它的意义在于,统一所有的外界任务到同一线程中,让调用者可以忽略线程同步问题。

3.线程池一般用法

3.1 一般方法介绍

  • shutDown(),关闭线程池,需要执行完已提交的任务;
  • shutDownNow(),关闭线程池,并尝试结束已提交的任务;
  • allowCoreThreadTimeOut(boolen),允许核心线程闲置超时回收;
  • execute(),提交任务无返回值;
  • submit(),提交任务有返回值;

3.2 newFixedThreadPool的使用

  • 3.2.1 创建一个newFixedThreadPool线程池
private void newFixedThreadPool() {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    for (int i = 1; i <= 20; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.e("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.2.2 打印日志如下
  • 创建了一个线程数为5的固定线程数量的线程池,同理该线程池支持的线程最大并发数也是5,模拟20个任务让它处理,执行任务。最后我们获取线程的信息,打印日志。
  • 日志如图所示:
    image

3.3 newSingleThreadExecutor的使用

  • 3.3.1 创建一个newSingleThreadExecutor线程池
private void newSingleThreadExecutor() {
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
    for (int i = 1; i <= number; i++) {
        final int index = i;
        singleThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.v("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.3.2 打印日志如下
  • 改了线程池的实现方式,即依次一个一个的处理任务,而且都是复用一个线程,日志为
    image

3.4 newCachedThreadPool的使用

  • 3.4.1 创建一个newCachedThreadPool线程池
private void newCachedThreadPool() {
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= number; i++) {
        final int index = i;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.v("潇湘剑雨newCachedThreadPool", "线程:" + threadName + ",正在执行第" + index + "个任务");
                try {
                    long time = index * 500;
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.4.2 打印日志如下
  • 为了体现该线程池可以自动根据实现情况进行线程的重用,而不是一味的创建新的线程去处理任务,我设置了每隔1s去提交一个新任务,这个新任务执行的时间也是动态变化的,所以,效果为:
    image

3.5 newScheduledThreadPool的使用

  • 3.5.1 创建一个newScheduledThreadPool线程池
private void newScheduledThreadPool() {
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟2秒后执行该任务
    scheduledThreadPool.schedule(new Runnable() {
        @SuppressLint("LongLogTag")
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Log.e("潇湘剑雨newScheduledThreadPool", "线程:" + threadName + ",正在执行");
        }
    }, 2, TimeUnit.SECONDS);
    //延迟1秒后,每隔2秒执行一次该任务
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Log.e("潇湘剑雨", "线程:" + threadName + ",正在执行");
        }
    }, 1, 2, TimeUnit.SECONDS);
}
  • 3.5.2 打印日志如下
  • 通过日志可以发现schedule方法的任务只是执行了一次,然后每隔2秒执行一次该scheduleAtFixedRate方法中的任务
    image

3.6 线程创建规则

  • ThreadPoolExecutor对象初始化时,不创建任何执行线程,当有新任务进来时,才会创建执行线程。构造ThreadPoolExecutor对象时,需要配置该对象的核心线程池大小和最大线程池大小
    1. 当目前执行线程的总数小于核心线程大小时,所有新加入的任务,都在新线程中处理。
    1. 当目前执行线程的总数大于或等于核心线程时,所有新加入的任务,都放入任务缓存队列中。
    1. 当目前执行线程的总数大于或等于核心线程,并且缓存队列已满,同时此时线程总数小于线程池的最大大小,那么创建新线程,加入线程池中,协助处理新的任务。
    1. 当所有线程都在执行,线程池大小已经达到上限,并且缓存队列已满时,就rejectHandler拒绝新的任务。

4.线程池封装

4.1 具体可以参考下篇文章

4.2 参考博客

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
9天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
20天前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
23天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
57 0
|
1月前
|
Java 调度 C#
C#学习系列相关之多线程(一)----常用多线程方法总结
C#学习系列相关之多线程(一)----常用多线程方法总结
|
1月前
|
安全 编译器 C#
C#学习相关系列之多线程---lock线程锁的用法
C#学习相关系列之多线程---lock线程锁的用法
|
1月前
|
Java C#
C#学习系列相关之多线程(五)----线程池ThreadPool用法
C#学习系列相关之多线程(五)----线程池ThreadPool用法
|
1月前
|
存储 安全 Java
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
线程使程序能够通过同时执行多个任务而更有效地运行。 线程可用于在不中断主程序的情况下在后台执行复杂的任务。 创建线程 有两种创建线程的方式。 扩展Thread类 可以通过扩展Thread类并覆盖其run()方法来创建线程:
102 1
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
|
1月前
|
数据采集 存储 Java
「多线程大杀器」Python并发编程利器:ThreadPoolExecutor,让你一次性轻松开启多个线程,秒杀大量任务!
「多线程大杀器」Python并发编程利器:ThreadPoolExecutor,让你一次性轻松开启多个线程,秒杀大量任务!
|
22天前
|
存储 算法 Java
【C/C++ 线程池设计思路】 深入探索线程池设计:任务历史记录的高效管理策略
【C/C++ 线程池设计思路】 深入探索线程池设计:任务历史记录的高效管理策略
68 0
|
16天前
|
安全 Java 容器
Java并发编程:实现高效、线程安全的多线程应用
综上所述,Java并发编程需要注意线程安全、可见性、性能等方面的问题。合理使用线程池、同步机制、并发容器等工具,可以实现高效且线程安全的多线程应用。
14 1

热门文章

最新文章