线程池详解一:线程池概念以及架构

java线程的创建非常昂贵,需要JVM和OS(操作系统)互相配合完成大量的工作。而java高并发频繁的创建和销毁线程的操作是非常低效的,如何降低java线程的创建成本,就必须要使用到线程池。

1 线程池的类继承体系

线程池的类继承体系image.png

1.1 Executor

它是 Java 异步目标任务的“执行者”接口,其目标是来执行目标任务。“执行者”Executor提供了 execute()接口来执行已提交的 Runnable 执行目标实例。Executor 作为执行者的角色,存在的目的是“任务提交者”与“任务执行者”分离开来的机制。

Executor

1.2 ExecutorService

ExecutorService 继承于 Executor。。它是 Java 异步目标任务的“执行者服务“接口,它对外提供异步任务的接收服务,ExecutorService 提供了“接收异步任务、并转交给执行者”的方法,如submit 系列方法、invoke 系列方法等等。

1.3 AbstractExecutorService

AbstractExecutorService 是一个抽象类 ,它 实 现 了 ExecutorService 接口。AbstractExecutorService 存在的目的是为 ExecutorService 中的接口提供了默认实现。

1.4 ThreadPoolExecutor

ThreadPoolExecutor就是“线程池”实现类,它继承于 AbstractExecutorService 抽象类。是 JUC 线程池的核心实现类。

1.5 ScheduledExecutorService

ScheduledExecutorService 是一个接口,它继承于于 ExecutorService。它是一个可以完成“延 时”“周期性”任务的调度线程池接口,其功能和 Timer/TimerTask 类似。

1.6 ScheduledThreadPoolExecutor


ScheduledThreadPoolExecutor 继承于 ThreadPoolExecutor,它提供了 ScheduledExecutorService线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。ScheduledThreadPoolExecutor 类似于 Timer , 但 是 在 高 并 发 程 序 中ScheduledThreadPoolExecutor 的性能要优于 Timer。

1.7 Executors

Executors 是个静态工厂类,它通 过 静 态 工 厂 方 法 返 回 ExecutorService 、ScheduledExecutorService 等线程池示例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法。

2 Executors 四种快捷创建线程池方法

2.1 newSingleThreadExecutor 创建“单线程化线程池”

也就是只有一条线程的线程池,所创建的线程池用唯一的工作线程来执行任务,使用此方法创建的线程池,能保证所有任务按照指定顺序(如FIFO)执行

newSingleThreadExecutor

public class TestSingleThreadExecutor {
    public static final int SLEEP_GAP = 500;

    /**
     * 异步任务的执行目标类
     */
    static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        private String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        @Override
        public void run() {
            Print.tco("任务:" + taskName + " doing");
            // 线程睡眠一会
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Print.tco(taskName + " 运行结束.");
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            pool.execute(new TargetTask());
            pool.submit(new TargetTask());
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

运行代码可以得出:

  1.单线程化的线程池中的任务,是按照提交的次序,顺序执行的
  2.池中的唯一线程的存活时间是无限的
  3.当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的。
  总体来说,单线程化的线程池所适用的场景是:任务按照提交次序,一个任务一个任务逐个执行的场景。

2.2 newFixedThreadPool 创建“固定数量的线程池”

newFixedThreadPool

public class TestNewFixedThreadPool {
    public static final int SLEEP_GAP = 500;

    /**
     * 异步任务的执行目标类
     */
    static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        private String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        @Override
        public void run() {
            Print.tco("任务:" + taskName + " doing");
            // 线程睡眠一会
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Print.tco(taskName + " 运行结束.");
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            pool.execute(new TargetTask());
            pool.submit(new TargetTask());
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

运行结果可得知:

  1.如果线程数量没有达到“固定数量”,则每次提交一个任务池内就创建一个新的线程,直到到达固定的数量
  2.线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  3.如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。

适用场景:需要任务长期执行的场景。“固定数量的线程池”的线程数能够比较稳定保证一个数,能够避免频繁回收线程和创建线程,故适用于处理 CPU 密集型的任务,在 CPU 被工作线程长时间使用的情况下,能确保尽可能少的分配线程。
弊端:内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无线增大,使服务器资源迅速耗尽。

2.3 newCachedThreadPool 创建“可缓存线程池”

newCachedThreadPool

public class TestNewCacheThreadPool {
    public static final int SLEEP_GAP = 500;

    /**
     * 异步任务的执行目标类
     */
    static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        private String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        @Override
        public void run() {
            Print.tco("任务:" + taskName + " doing");
            // 线程睡眠一会
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Print.tco(taskName + " 运行结束.");
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.execute(new TargetTask());
            pool.submit(new TargetTask());
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

“可缓存线程池”的特点,大致如下:

  1.在接收新的异步任务 target 执行目标实例时,如果池内所有线程繁忙,此线程池会添加新线程来处理任务。
  2.此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
  3.如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,那么就会回收空闲(60 秒不执行任务)线程。

适用场景:需要快速处理突发性强、耗时较短的任务场景,如 Netty 的NIO 处理场景、REST API 接口的瞬时削峰场景。“可缓存线程池”的线程数量不固定,只要有空闲线程就会被回收;接收到的新异步任务执行目标,查看是否有线程处于空闲状态,如果没有就直接创建新的线程。
弊端:线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能导致创线程过多会而导致资源耗尽。

2.4 newScheduledThreadPool 创建“可调度线程池”

创建一个可调度线程池,池内仅含有一条线程:

newSingleThreadScheduledExecutor

创建一个可调度线程池,池内含有 N 条线程,N 的值为输入参数 corePoolSize:

newScheduledThreadPool

总结

创建的线程池的好处?

  1.降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
  2.提高响应速度.当任务到达时,任务可以不需要等到线程和粗昂就爱你就能立即执行.
  3.提高线程的可管理性.线程是稀缺资源,如果无限的创线程,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控

以上是通过 JUC 的 Executors 四个主要的快捷创建线程池方法。不同类型的线程池,其实都是由前面的几个关键配置参数配置而成的。在《阿里巴巴Java开发手册》中,明确禁止使用Executors创建线程池,并要求开发者直接使用ThreadPoolExector或
ScheduledThreadPoolExecutor进行创建。这样做是为了强制开发者明确线程池的运行策略,使其对线程池的每个配置参数皆做到心中有数,以规避因使用不当而造成资源耗尽的风险。


作者:干天慈雨
链接:https://www.jianshu.com/p/c03f21033153

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值