(十二)线程池的创建、调度流程、拒绝策略和状态

1. 线程池原理

Java线程需要JVM和操作系统配合完成大量的工作

  1. 必须为线程堆栈分配和初始化大量内存块,其中包含至少1M的栈内存
  2. 需要进行系统调用,以便在操作系统中创建和注册本地线程
    频繁创建和销毁线程非常低效,通过使用线程池可以提升性能,方便线程的管理
    请添加图片描述

2. JUC的线程池架构

JUC就是java.util.concurrent工具包的简称,该工具包是从 JDK 1.5 开始加入到 JDK,用于完成高并发、处理多线程的一个工具包。
JUC中的线程池如图所示:
请添加图片描述

  • Executor
    它是Java异步目标任务的“执行者”接口,其目的是来执行目标线程void execute(Runnable command)
  • ExecutorService
    ExecutorService 提供了“接收异步任务、并转交给执行者”的方法,如submit 系列方法、 invoke 系列方法等等。
//向线程池提交单个异步任务
 Future submit(Callable task);
//向线程池提交批量异步任务
 List<Future> invokeAll(Collection<? extends Callable> tasks) throws InterruptedException;
  • AbstractExecutorService
    AbstractExecutorService 存在的目的是为 ExecutorService 中的接口提供了默认实现。
  • ThreadPoolExecutor
    ThreadPoolExecutor 就是“线程池”实现类。ThreadPoolExecutor 是 JUC 线程池的核心实现类。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。
  • ScheduledThreadPoolExecutor
    它提供了 ScheduledExecutorService线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。
  • Executors
    Executors 是 个 静 态 工 厂 类 , 它 通 过 静 态 工 厂 方 法 返 回 ExecutorService、ScheduledExecutorService 等线程池实例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法。

3. Executors创建线程池

java中通过Executor工厂类提供四种快捷创建线程池的方法,如下:

方法名功能简介
newSingleThreadExecutor()创建只有一个线程的线程池
newFixedThreadPool(int nThreads)创建固定大小的线程池
newCachedThreadPool()创建一个不限制线程数量的线程池,任何提交的任务都将立即执行,但是空闲线程会得到及时回收
newScheduledThreadPool()创建一个可定期或者延时执行任务的线程池

3.1 newSingleThreadExecutor

创建单线程化线程池该线程池中只有一个线程来工作,此线程池能保证所有任务按照指定的顺序执行

public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        public void run() {
            System.out.println("任务:" + taskName + " doing");
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(taskName + " 运行结束.");
            countDownLatch.countDown();
        }
        @Override
        public String toString() {
            return "TargetTask{" + taskName + '}';
        }
    }
@Test
    public void testSingleThreadExecutor() {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < MAX_TURN; i++) {
            pool.execute(new TargetTask());//相当于新建线程并使线程start()
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
//        //关闭线程池
        pool.shutdown();
    }

运行结果

任务:task-1 doing
task-1 运行结束.
任务:task-2 doing
task-2 运行结束.
任务:task-3 doing
task-3 运行结束.
任务:task-4 doing
task-4 运行结束.
任务:task-5 doing
task-5 运行结束.

特点:

  1. 存活时间无限
  2. 任务实例会进入无界的阻塞队列

3.2 newFixedThreadPool

创建固定数量的线程池其唯一的参数用于设置池中线程的“固定数量”。

public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        public void run() {
            System.out.println("任务:" + taskName + " doing");
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(taskName + " 运行结束.");
            countDownLatch.countDown();
        }
        @Override
        public String toString() {
            return "TargetTask{" + taskName + '}';
        }
    }
 @Test
    public void testNewFixedThreadPool() throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);//核心线程设置为3个
        for (int i = 0; i < MAX_TURN; i++) {
            pool.execute(new TargetTask());
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //关闭线程池
        pool.shutdown();
    }

运行结果

任务:task-1 doing
任务:task-3 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.
task-3 运行结束.
任务:task-5 doing
任务:task-4 doing
task-4 运行结束.
task-5 运行结束.

特点:

  1. 没有达到“固定数量”,每一次提交会创建一个新线程,直到达到线程的"固定数量"
  2. 一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  3. 新来的执行目标实例会进入无界的阻塞队列

3.3 newCachedThreadPool

创建可缓存线程池如果线程内的某些线程无事可干成为空闲线程,“可缓存线程池”可灵活收回这些空闲线程。

public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        public void run() {
            System.out.println("任务:" + taskName + " doing");
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(taskName + " 运行结束.");
            countDownLatch.countDown();
        }
        @Override
        public String toString() {
            return "TargetTask{" + taskName + '}';
        }
    }
@Test
    public void testNewCachedThreadPool() throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < MAX_TURN; i++) {
            pool.execute(new TargetTask());
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //关闭线程池
        pool.shutdown();
    }

运行结果

任务:task-1 doing
任务:task-3 doing
任务:task-2 doing
任务:task-4 doing
任务:task-5 doing
task-5 运行结束.
task-4 运行结束.
task-3 运行结束.
task-2 运行结束.
task-1 运行结束.

特点:

  1. 在接收新任务时,如果池内所有线程都在运行,此线程池会添加新线程来处理任务。
  2. 线程池的大小不限,完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小
  3. 如果空闲线程60秒内不执行任务将会被收回。
  4. 弊端:线程池没有数量限制,如果大量的任务执同时提交,可能导致创线程过多会而导致资源耗尽。

3.4 newScheduledThreadPool

创建可调度线程池一个提供“延时”和“周期性”任务的调度功能的ScheduledExecutorService类型的线性池。用法如下:

//方法一:创建一个池内仅含有一条线程的线性池
public static ScheduledExecutorService newSingleThreadScheduledExecutor();
//方法二:创建一个可调度线程池,池内含有 N 条线程, N 的值为输入参数 corePoolSize
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) ;
public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        public void run() {
            System.out.println("任务:" + taskName + " doing");
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(taskName + " 运行结束.");
            countDownLatch.countDown();
        }
        @Override
        public String toString() {
            return "TargetTask{" + taskName + '}';
        }
    }
 @Test
    public void testNewScheduledThreadPool() {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        for (int i = 0; i < 2; i++) {
            pool.scheduleAtFixedRate(new TargetTask(),
                    0, 5000, TimeUnit.MILLISECONDS);
        }
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //关闭线程池
        pool.shutdown();
    }

运行结果

任务:task-1 doing
任务:task-2 doing
task-1 运行结束.
task-2 运行结束.//间隔5秒再打印
任务:task-1 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.
任务:task-1 doing
任务:task-2 doing
task-2 运行结束.
task-1 运行结束.

两种方法:
1.scheduleAtFixedRate 是从任务开始时算起
2.scheduleWithFixedDelay是从任务结束时算起

4. 线程池的标准创建

大部分企业的开服规范都会禁止使用快捷线程池,要求通过标准构造器ThreadpoolExecutor去构造工作线性池,

使用标准构造器,构造一个普通的线程池
public ThreadPoolExecutor(
    int corePoolSize,  核心线程数,即使线程空闲(Idle),也不会回收;
    int maximumPoolSize,  线程数的上限;
    long keepAliveTime, TimeUnit unit,  线程最大空闲(Idle)时长
    BlockingQueue workQueue,  任务的排队队列
    ThreadFactory threadFactory,  新线程的产生方式
    RejectedExecutionHandler handler)  拒绝策略
  • corePoolSize 核心线程池数,maximumPoolSize 最大线程数。线程池规则如下:

    1. 当有新任务加入时,并且当前工作线程数少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求,直到线程数达到 corePoolSize
    2. 当前工作线程数多于 corePoolSize 数量,但小于 maximumPoolSize 数量,则仅当任务排队队列已满时,才会创建新线程。
    3. corePoolSize 和 maximumPoolSize 不 仅 能 在 线 程 池 构 造 时 设 置 , 也 可 以 使 用setCorePoolSize 和 setMaximumPoolSize 两个方法进行动态更改。
  • BlockingQueue(阻塞队列)
    实例用于暂时接收到的异步任务,如果线程池的核心线程都在忙,则所接收到的目标任务,缓存在阻塞队列中。

  • KeepAliveTime(存活时间)

  • Unit(时间单位)

  • handler(拒绝策略)

5. 线程池的任务调度流程

在这里插入图片描述
(1)当前线程数小于核心线程池数量,执行器总是优先创建一个任务线程
(2)线程池中任务数大于核心线程池数,任务将被加入到阻塞队列中,一直到阻塞队列满。
(3)当完成一个任务的执行时,执行器总是从阻塞队列中取任务并开始执行,直到阻塞队列为空
(4)在核心线程池数量已经用完、阻塞队列也已经满的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。
(5)在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出 maximumPoolSize。如果线程池的线程总数超时 maximumPoolSize,则线程池会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略

public static final int SLEEP_GAP = 500;
public static final int MAX_TURN = 5;
public static CountDownLatch countDownLatch = new CountDownLatch(MAX_TURN);
public static class TargetTask implements Runnable {
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }
        public void run() {
            System.out.println("任务:" + taskName + " doing");
            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(taskName + " 运行结束.");
            countDownLatch.countDown();
        }
        @Override
        public String toString() {
            return "TargetTask{" + taskName + '}';
        }
    }
    @Test使用标准的构造器,构造一个普通的线程池
    public void testThreadPoolExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, //corePoolSize
                4,  //maximumPoolSize
                100, //keepAliveTime
                TimeUnit.SECONDS, //unit
                new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());//workQueue

        for (int i = 0; i < 6; i++) {
            final int taskIndex = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "   taskIndex = " + taskIndex);
                    try {
                        Thread.sleep(10000);
//                        Thread.sleep(Long.MAX_VALUE);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        while (true) {
            //每隔 1 秒,输出线程池的工作任务数量、总计的任务数量
            System.out.printf("- activeCount: %d - taskCount: %d\r\n", executor.getActiveCount(), executor.getTaskCount());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

运行结果

pool-1-thread-2   taskIndex = 1
pool-1-thread-1   taskIndex = 0
pool-1-thread-3   taskIndex = 4
pool-1-thread-4   taskIndex = 5
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
- activeCount: 4 - taskCount: 6
pool-1-thread-1   taskIndex = 2
pool-1-thread-4   taskIndex = 3
- activeCount: 2 - taskCount: 6
- activeCount: 2 - taskCount: 6
- activeCount: 2 - taskCount: 6
- activeCount: 0 - taskCount: 6

6. 线程池的拒绝策略

任务被拒绝有两种情况:

  • 线程池已经被关闭。
  • 工作队列已满且 maximumPoolSize 已满。
    无 论 以 上 哪 种 情 况 任 务 被 拒 , 线 程 池 都 会 调 用 RejectedExecutionHandler 实 例 的rejectedExecution 方法。RejectedExecutionHandler是拒绝策略的接口, JUC 为该接口提供了以下几种实现:
    1. AbortPolicy
    2. DiscardPolicy
    3. DiscardOldestPolicy
    4. CallerRunsPolicy
    5. 自定义策略

6.1 AbortPolicy

直接抛出异常RejectedExecutionException默认的拒绝策略

6.2 DiscardPolicy

新任务会直接被丢掉,并且不会有任何异常抛出。

6.3 DiscardOldestPolicy

抛弃最老的任务策略,会将最早进入队列的任务抛弃后,新来的任务再尝试入队

6.4 CallerRunsPolicy

调用者执行策略。在新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务。

7. 线程池的状态

有五种具体为:

  • RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。
  • SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕。
  • STOP:该状态下线程池不再接受新任务,也不会处理工作队列中的剩余任务,并且将会中断所有工作线程。
  • TIDYING:该状态下所有任务都已终止或者处理完成,将会执行terminated( )钩子方法。
  • TERMINATED:执行完terminated( )钩子方法之后的状态。

在这里插入图片描述
线程池的状态转换规则为:
(1)线程池创建之后状态为 RUNNING。
(2)执行线程池的 shutdown 实例方法,会使线程池状态从 RUNNING 转变为 SHUTDOWN。
(3)执行线程池的 shutdownNow 实例方法,会使线程池状态从 RUNNING 转变为 STOP。
(4)当线程池处于 SHUTDOWN 状态,执行器 shutdownNow 方法,会将其状态转变为 STOP状态。
(5)等待线程池的所有工作线程停止,工作队列清空之后,线程池状态会从 STOP 转变为TIDYING。
(6)执行完 terminated( ) 钩子方法之后,线程池状态从 TIDYING 转变为 TERMINATED

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值