java---线程池详解

目录

一、为什么需要线程池

 1.线程池介绍

2.线程池的优点

 1.复用线程,降低资源消耗

2.提高响应速度

3.管控线程数和任务数

 二、线程池的创建方式

三、线程池七个参数含义

1.corePoolSize 

2.maximumPoolSize

3.keepAliveTime

4.TimeUnit

5.BlockingQueue

6.ThreadFactory

7.RejectedExecutionHandler

四、线程池如何执行

1.演示AbortPolicy拒绝策略

总结



一、为什么需要线程池

 1.线程池介绍

线程池是线程使用的一种模式,它将线程和任务的概念分离开,使用线程来执行任务,并提供统一的线程管理和任务管理的实现方法,避免了频繁创建和销毁线程所带来的性能开销。        

2.线程池的优点

线程池相比于线程来说,它不需要频繁的创建和销毁线程,线程一旦创建之后,默认情况下就会一直保持在线程池中,等到有任务来了,再用这些已有的线程来执行任务,如下图所示:

 1.复用线程,降低资源消耗

线程在创建时要开辟虚拟机栈、本地方法栈、程序计数器等私有线程的内存空间,而销毁时又要回收这些私有空间资源。而线程池创建了线程之后就会放在线程池中,因此线程池相比于线程来说,第一个优点就是可以复用线程、减低系统资源的消耗

2.提高响应速度

线程池是复用已有线程来执行任务的,而线程是在有任务时才新建的,所以相比于线程来说,线程池能够更快的响应任务和执行任务。

3.管控线程数和任务数

  1. 控制最大并发数:线程池可以创建固定的线程数,从而避免了无限创建线程的问题。当线程创建过多时,会导致系统执行变慢,因为 CPU 核数是一定的、能同时处理的任务数也是一定的,而线程过多时就会造成线程恶意争抢和线程频繁切换的问题,从而导致程序执行变慢,所以合适的线程数才是高性能运行的关键。

  2. 控制任务最大数:如果任务无限多,而内存又不足的情况下,就会导致程序执行报错,而线程池可以控制最大任务数,当任务超过一定数量之后,就会采用拒绝策略来处理多出的任务,从而保证了系统可以健康的运行。

 

 二、线程池的创建方式

在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:

  • 通过 ThreadPoolExecutor 手动创建线程池。

  • 通过 Executors 执行器自动创建线程池。

 

而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。

  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。

  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。

  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

  7. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险。 

三、线程池七个参数含义

所谓的线程池的 7 大参数是指,在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数,如以下源码所示:

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

这 7 个参数分别是:

  1. corePoolSize:核心线程数。

  2. maximumPoolSize:最大线程数。

  3. keepAliveTime:空闲线程存活时间。

  4. TimeUnit:时间单位。

  5. BlockingQueue:线程池任务队列。

  6. ThreadFactory:创建线程的工厂。

  7. RejectedExecutionHandler:拒绝策略。

1.corePoolSize 

核心线程数:是指线程池中长期存活的线程数。

这就好比是一家公司里面的正式工,在公司里的工作比较稳定,正常情况下,不会被辞退,即使干的活少。

2.maximumPoolSize

最大线程数:线程池允许创建的最大线程数量,当线程池的任务队列满了之后,可以创建的最大线程数。

最大数量包含正式工 + 临时工,就好比公司的某一段时间比较的忙,正式工的工作干不完,就会临时的找一些人来帮助工作,但是一共的认识不可以超过maximumPoolSize。

注意:最大线程数 maximumPoolSize 的值不能小于核心线程数 corePoolSize,否则在程序运行时会报 IllegalArgumentException 非法参数异常。

3.keepAliveTime

空闲线程存活时间,当线程池中没有任务时,会销毁一些线程,销毁的线程数=maximumPoolSize(最大线程数)-corePoolSize(核心线程数)。

就好比公司很忙的时间段过去了,一些临时工在一定的时间都没有工作干,那么公司就会解雇这个临时工。这里的一定的时间,就是keepAliveTime来确定的。

4.TimeUnit

时间单位:空闲线程存活时间的描述单位,此参数是配合参数 3 使用的。参数 3 是一个 long 类型的值,比如参数 3 传递的是 1,那么这个 1 表示的是 1 天?还是 1 小时?还是 1 秒钟?是由参数 4 说了算的。TimeUnit 有以下 7 个值:

  1. TimeUnit.DAYS:天

  2. TimeUnit.HOURS:小时

  3. TimeUnit.MINUTES:分

  4. TimeUnit.SECONDS:秒

  5. TimeUnit.MILLISECONDS:毫秒

  6. TimeUnit.MICROSECONDS:微妙

  7. TimeUnit.NANOSECONDS:纳秒

5.BlockingQueue

阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务,主要常见的有以下:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。

  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

6.ThreadFactory

线程工厂:线程池创建线程时调用的工厂方法,通过此方法可以设置线程的优先级、线程命名规则以及线程类型(用户线程还是守护线程)等。线程工厂的使用示例如下:

public static void main(String[] args) {
    // 创建线程工厂
    ThreadFactory threadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            // 创建线程池中的线程
            Thread thread = new Thread(r);
            // 设置线程名称
            thread.setName("Thread-" + r.hashCode());
            // 设置线程优先级(最大值:10)
            thread.setPriority(Thread.MAX_PRIORITY);
            //......
            return thread;
        }
    };
    // 创建线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0,
                                                                   TimeUnit.SECONDS,
                                                             new LinkedBlockingQueue<>(),
                                                                   threadFactory); // 使用自定义的线程工厂
    threadPoolExecutor.submit(new Runnable() {
        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            System.out.println(String.format("线程:%s,线程优先级:%d",
                                             thread.getName(), thread.getPriority()));
        }
    });
}

7.RejectedExecutionHandler

拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略。默认的拒绝策略有以下 4 种: 

  • AbortPolicy:拒绝并抛出异常。

  • CallerRunsPolicy:使用当前调用的线程来执行此任务。

  • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。

  • DiscardPolicy:忽略并抛弃当前任务。

线程池的默认策略是 AbortPolicy 拒绝并抛出异常。

四、线程池如何执行

想要真正的了解线程池的执行流程,就得先从线程池的执行方法 execute() 说起,execute() 实现源码如下:    

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 当前工作的线程数小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 创建新的线程执行此任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 检查线程池是否处于运行状态,如果是则把任务添加到队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次检线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
        // 如果是非运行状态,则将刚加入队列的任务移除
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false); // 新建线程执行任务
    }
    // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
    else if (!addWorker(command, false)) 
        // 执行拒绝策略
        reject(command);
}

从上述源码我们可以看出,当任务来了之后,线程池的执行流程是:先判断当前线程数是否大于核心线程数?如果结果为 false,则新建线程并执行任务;如果结果为 true,则判断任务队列是否已满?如果结果为 false,则把任务添加到任务队列中等待线程执行,否则则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务,否则将执行线程池的拒绝策略。 

1.演示AbortPolicy拒绝策略

代码示例:

public static void main(String[] args) {
    // 任务的具体方法
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new ThreadPoolExecutor.AbortPolicy()); // 显式指定拒绝策略,也可以忽略此设置,它为默认拒绝策略
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    // 线程池执行完任务,关闭线程池
    threadPool.shutdown();
}

运行结果:

从结果可以看出,给线程池添加了 4 个任务,线程池正常执行了 2 个任务,其他两个任务执行了中止策略,并抛出了拒绝执行的异常 RejectedExecutionException。


总结

加油哦~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值