多线程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 参考博客

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

前言介绍

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 参考博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 为什么使用线程池 当我们在使用线程时,如果每次需要一个线程时都去创建一个线程,这样实现起来很简单,但是会有一个问题...
    闽越布衣阅读 4,240评论 10 45
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,702评论 2 20
  • 深入分析线程池 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如...
    史路比阅读 444评论 0 1
  • 1、定义一个事件: 2、对UITextField控件添加监听事件:
    远行客丶阅读 210评论 0 0
  • 早早就醒了,天还没亮,窗前黑黑的。 翻个身继续睡,可是事实证明确实醒了。索性上了厕所,看见热水器开着,又起身关掉。...
    六月微尘阅读 215评论 0 0