Java 线程池系列(上)之线程池是什么东东?

在学习任何东西之前,需要多反问自己,这个东西是什么(What)、有什么用(Where)、有什么优缺点(Why)、怎么用(How)。这样才能清楚学习的目的而不是盲目的跟风学习。

简述

作为线程池系列的第一篇文章,咱们先聊聊线程池是个什么东东。

线程的创建和销毁对于系统来说是一种开销,而使用线程池可以复用线程,减少线程的创建和销毁操作,提高系统的性能。另外一方面,线程池能够做到资源管理和限制。比方说线程池可以限制线程的数量,避免无限的创建线程导致OOM,在高并发的场景下尤为关键。

那使用线程池有什么好处?

  • 重用存在的线程,减少线程对象创建、销毁的开销,提升性能。
  • 充分利用有限的线程资源,同时避免过多资源竞争,避免堵塞。
  • 减少线程调度的开销
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

emmm... 大概了解线程池是什么了,那线程池的使用场景有什么呢?

线程池的使用场景十分广泛,因为很少在实际工作中直接使用new Thread这种方式创建线程,一般都会使用线程池管理线程的创建和销毁。实际上,很多框架底层大量的使用线程池,如RocketMQ底层大量的使用定时执行的线程池,发送和接收各个服务的心跳信息等等

线程池真强大,那使用线程池正确的姿势是什么呢?

实际上,J.U.C包下已经贴心的为我们准备了几种线程池满足我们大部分的场景需求,而且我们也可以自定义线程池来满足我们项目实际场景需求。

Executors是线程池的工厂类,我们可以通过Executors工厂类快速的创建以下几种线程池:


  • newFixedThreadPool():创建一个指定工作线程数量的线程池。主要被应用在线程资源有限,数据量较小或不可控场景,由于其线程数量有限,针对于过多的数据量,默认将会进行丢弃
  • newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。主要被应用在响应时间要求高、数据量可控的场景,由于其不限制创建线程的个数,故若数据量不可控,会造成程序 OOM
  • newSingleThreadExecutor():创建一个单线程的线程池。如果执行的线程异常退出会由其它线程取代
  • newScheduledThreadPool():创建一个可以定时及周期性任务执行的线程池
  • newSingleThreadScheduledExecutor():与newScheduledThreadPool相似,只不过是单线程
  • newWorkStealingPool():创建一个具有抢占式操作的线程池

以上几种线程池的使用,咱们就可以当成一个黑盒子使用,需要的时候直接创建出来,但底层的原理万变不离其宗,因为以上的线程池对象都是创建ThreadPoolExecutor初始化的(newWorkStealingPool线程池是通过ForkJoinPool),因此咱们学习线程池的重点应该是学习ThreadPoolExecutor类。

入门使用线程池

在了解ThreadPoolExecutor之前,咱们先从一个Demo开始入手吧!

public class ThreadPoolDemo {
    private static final String filePath1 = "E:/JavaTest/test1.txt";
    private static File file1 = new File(filePath1);

    // 数据量
    private static final int num = 2000 * 500;

    public static void main(String[] args) {
        /**
         * 创建线程池
         * coreSize:4
         * maximumPoolSize:4
         * keepAliveTime 和 TimeUnit.SECONDS:线程存活时间为1秒
         * threadFactory:使用默认的线程工厂
         * RejectedExecutionHandler:拒绝策略为AbortPolicy
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 1, TimeUnit.SECONDS,
                new ArrayBlockingQueue(20),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        try {
            // 创建10个任务提交到线程池
            for (int i = 0; i < 10; i++) {
                threadPoolExecutor.submit(() -> {
                    try {
                        // 调用io操作的方法,模拟线程执行的耗时,方便监控工具的查看
                        writeFile(file1);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        } finally {
            threadPoolExecutor.shutdown();
        }
    }

    // 写文件的IO操作
    static void writeFile(File file) throws IOException {
        // 判断是否有该文件
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //创建输出缓冲流对象
        BufferedWriter bufferedWriter = null;
        try {
            bufferedWriter = new BufferedWriter(new FileWriter(file));
            for (int i = 0; i < num; i++) {
                try {
                    bufferedWriter.write(i);
                    bufferedWriter.newLine();
                    bufferedWriter.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " " + new Date() + " 执行完成");
        }finally {
            bufferedWriter.close();
        }
    }
}

运行结果:

pool-1-thread-1 Mon Apr 06 22:14:49 CST 2020 执行完成
pool-1-thread-2 Mon Apr 06 22:14:49 CST 2020 执行完成
pool-1-thread-3 Mon Apr 06 22:14:49 CST 2020 执行完成
pool-1-thread-4 Mon Apr 06 22:14:49 CST 2020 执行完成
pool-1-thread-3 Mon Apr 06 22:15:03 CST 2020 执行完成
pool-1-thread-1 Mon Apr 06 22:15:03 CST 2020 执行完成
pool-1-thread-2 Mon Apr 06 22:15:03 CST 2020 执行完成
pool-1-thread-4 Mon Apr 06 22:15:03 CST 2020 执行完成
pool-1-thread-1 Mon Apr 06 22:15:10 CST 2020 执行完成
pool-1-thread-3 Mon Apr 06 22:15:10 CST 2020 执行完成

使用Java自带的VisualVM监控工具查看线程的状况


总结:

结合输出结果和监控工具的视图,程序的确是创建了4个线程执行任务。例子中线程池相关的代码其实就只是调用submit方法就可以很方便的指定创建多少线程执行任务。其实线程池的骚操作远远不止如此!接下来走进线程池源码,更加深入线程池是怎么创建线程,怎么管理线程,看下有什么设计思想值得学习。

下一篇文章:Java 线程池系列(下)之 ThreadPoolExecutor 源码剖析

如果觉得文章不错的话,麻烦点个赞哈,你的鼓励就是我的动力!对于文章有哪里不清楚或者有误的地方,欢迎在评论区留言~

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