线程池

1线程池的使用及其优势

1.1 为什么要使用线程池

  • 降低资源的消耗:通过复用已经创建好的线程来降低 重复创建线程和消耗线程所带来的资源消耗
  • 提高响应速度:任务来临的时候,不需要等待线程的创建就可以直接使用
  • 提高线程的可管理性:线程是稀缺资源,无限制的创建线程会造成不仅会不必要的系统资源损耗,还会降低系统的稳定性。使用线程池可以很直观的控制最大线程数,对线程进行统一的监控,也方便了调优工作。

1.2 jdk提供的常见线程池

       //定容的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        //仅允许一个线程同时存在的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //不固定容量的线程(会随着任务数的增加而改变活跃线程数)
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

1.3 常见线程池的使用

分别定义了三个不同类型的线程池,使用for循环模拟10个任务请求。
观察打印的结果可以发现:
Executors.newFixedThreadPool(5)最多有5个线程工作
Executors.newSingleThreadExecutor()最多有1个线程工作
Executors.newCachedThreadPool()根据任务量的多少来动态产生线程数

public class NormolThreadpool {
    public static void main(String[] args) {
        String yeu = "办理业务";
        //定容的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        //仅允许一个线程同时存在的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //不固定容量的线程(会随着任务数的增加而改变活跃线程数)
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        System.out.println("----------FixedThreadPool----------------");
        try {
            for (int i = 1; i <= 10; i++) {
                fixedThreadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + yeu);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //但凡涉及到池之类的资源 都要记得关闭
            fixedThreadPool.shutdown();
        }

        try {
            TimeUnit.SECONDS.sleep(1L);

        } catch (Exception e) {

            e.printStackTrace();
        }

        System.out.println("----------CacheThreadPool----------------");
        try {
            for (int i = 1; i <= 10; i++) {
                cachedThreadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + yeu);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //但凡涉及到池之类的资源 都要记得关闭
            cachedThreadPool.shutdown();
        }


        try {
            TimeUnit.SECONDS.sleep(1L);

        } catch (Exception e) {

            e.printStackTrace();
        }

        System.out.println("----------SingleThreadPool----------------");
        try {
            for (int i = 1; i <= 10; i++) {
                singleThreadExecutor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + yeu);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //但凡涉及到池之类的资源 都要记得关闭
            singleThreadExecutor.shutdown();
        }
    }
}

打印结果:

----------FixedThreadPool----------------
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-1办理业务
pool-1-thread-2办理业务
pool-1-thread-3办理业务
pool-1-thread-4办理业务
pool-1-thread-5办理业务
----------CacheThreadPool----------------
pool-3-thread-1办理业务
pool-3-thread-2办理业务
pool-3-thread-3办理业务
pool-3-thread-4办理业务
pool-3-thread-5办理业务
pool-3-thread-1办理业务
pool-3-thread-2办理业务
pool-3-thread-1办理业务
pool-3-thread-5办理业务
pool-3-thread-4办理业务
----------SingleThreadPool----------------
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务
pool-2-thread-1办理业务

1.4 常见线程池的源码分析

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

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 可以发现他们底层都调用了一个ThreadPoolExecutor线程池的底层实现都要靠这个)类的构造函数,这个构造函数的参数先不谈。
  • 同时还可以注意到newFixedThreadPoolnewSingleThreadExecutor的第五个参数都用到了LinkedBlockingQueue<Runnable>,而jdk对它的描述是

Creates a LinkedBlockingQueue with a capacity of Integer.MAX_VALUE.`

进一步的深究:

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

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

可以发现:

  • 真正用来做实际事情的是这个7个参数的构造方法

2.线程池的7大参数(以银行为例)

  • corePoolSize:池内核心线程数,相当于银行中当前值班的窗口数量
  • maximumPoolSize:池内最大线程数---银行中所有的窗口数量,包含corePoolSize
  • keepAliveTime:保持线程活跃时间。
  • unit:时间单位
  • workQueue:阻塞队列 ----办理业务时窗口都有人,在休息区等待的人
  • threadFactory:线程工厂
  • handler:等待队列也已经排满了,再也塞不下新的任务了 同时,线程池的max也到达了,无法接续为新任务服务。这时我们需要拒绝策略机制合理的处理这个问题.

2.1线程池的底层原理(执行流程)

假定 corePoolSize=2 maximumPoolSize=5 keepAliveTime=2L unit=TimeUnit.SECENDS
workQueue的容量为3

  1. 最开始任务提交的时候,线程池直接接收请求。
  2. corePoolSize满了之后,如果还有新的任务请求,会判断workQueue是否已满,
  3. 如果workQueue没满 就让任务请求进去阻塞队列等待
  4. 如果workQueue也满了,就会激活新的线程,总线程数不能超过maximumPoolSize
  5. 如果还有新的任务请求进来 同时 maximumPoolSize也满了,那就需要用到拒绝策略
    自定义的线程池案例:
public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                //抛异常的拒绝策略RejectedExecutionException
                //new ThreadPoolExecutor.AbortPolicy()
                //把任务交还给调用此线程的线程去执行
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //抛弃队列中等待最久的任务,然后把自己加入队列
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                //直接丢弃任务,如果任务可用丢弃这是最好的策略
                new ThreadPoolExecutor.DiscardPolicy()
        );
        try {
            //模拟十个任务 需要开启10个线程
            for (int i = 0; i < 10; i++) {
                threadPoolExecutor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "处理中---");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }

    }

2.2 线程池的选择

都不用,参考阿里巴巴开发手册。

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

2.3 如何合理的配置线程池

2.3.1 CPU密集型

说明:需要大量运算没有阻塞,cpu一直全速运行(单核cpu的话无论开几个线程都一样)
公式:CPU核数+1 个线程数 尽可能少的线程数
Runtime.getRuntime().availableProcessors() 获取cpu核心数

2.3.2 IO密集型

因为需要大量的io,意味着大量的IO所以要尽可能多的线程。
(cpu数-1)/阻塞系数 (0.8-0.9)
或者
cpu*2

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

推荐阅读更多精彩内容