充分讲解什么是线程池

文章介绍了线程池的概念、引入原因及其核心原理,包括如何解决线程频繁创建和销毁的弊端。线程池通过创建、复用线程来提高效率,同时讨论了无上限和有上限线程池的创建及示例。此外,文章还探讨了自定义线程池的参数,如核心线程数、最大线程数、阻塞队列和任务拒绝策略等,并举例说明了不同任务量下的线程池行为。
摘要由CSDN通过智能技术生成

线程池

1. 弊端

弊端一:用到线程的时候就创建

class MyThreadextends Thread(
    @Override
    public void run() {
        // 多线程执行的一些代码
}

弊端二:用完之后线程消失

MyThread t1 = new MyThread();
MyThread t2 = new MyThread();

t1.start();
t2.start();

之前我们需要线程的时候就会创建对象,而用完之后,线程就会消失,如此会浪费操作系统的资源

为解决以上弊端,我们引入线程池,线程池实际上就是一个存储线程的一个容器

2. 引入

刚开始,线程池里面是空的,当我们把任务提交给线程池时,线程池本身就会自动创建一个线程,任务一就会拿着线程去完成任务

特殊情况:当任务一还未执行完成,而任务二已经被提交到线程池,此时,线程池就会继续创建新的线程对象,任务二从而可以拿着新的线程去执行任务
在这里插入图片描述
注:线程池的线程创建线程是有上限的,而这个上限可以自己设置

3. 核心原理

  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

4. 创建线程池

  • 创建线程池

  • 提交任务

  • 所有的任务全部执行完毕,关闭线程池

注:线程池一般不会关闭,服务器都是二十四小时运行,服务器不关闭,任务就随时都可能有,线程池就不会关闭

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池
public staticExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池

这里的“没有上限”指的并不是真正意义上的没有上限,其最大值为int类型的最大值(21多个亿),但不需要考虑线程不够用的情况,线程池还没创建这么多线程时,电脑就已经崩溃了

4.1 创建没有上限的线程池

测试类:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test1 {
    public static void main(String[] args) {
        // 获取线程池对象
        ExecutorService pool = Executors.newCachedThreadPool();

        // 提交任务(多个任务)
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        // 销毁线程池
        pool.shutdown();
    }
}

MyRunnable类:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

运行结果:
在这里插入图片描述
由此可见,线程池所创建了四个线程对象去执行任务

4.2 线程的复用演示:

测试类:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test1 {
    public static void main(String[] args) throws InterruptedException {
        // 获取线程池对象
        ExecutorService pool = Executors.newCachedThreadPool();

        // 提交任务(多个任务)
        pool.submit(new MyRunnable());
        // 让上一个任务完成任务将线程还给线程池
        // 结果就是一直使用线程一去执行任务
        Thread.sleep(1000);
        pool.submit(new MyRunnable());
        Thread.sleep(1000);
        pool.submit(new MyRunnable());
        Thread.sleep(1000);
        pool.submit(new MyRunnable());

        // 销毁线程池
        pool.shutdown();
    }
}

MyRunnable类:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->");
        //}
    }
}

运行结果:
在这里插入图片描述
由于在执行下一个任务之前,让线程先睡几秒导致之前的任务已经执行完毕,之后的任务就会一直使用第一个线程

4.3 创建有上限的线程池

测试类:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test1 {
    public static void main(String[] args) {
        // 获取线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 提交任务(多个任务)
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        // 销毁线程池
        pool.shutdown();
    }
}

MyRunnable类:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

运行结果:
在这里插入图片描述
运行结果中只有线程1、2、3,上面测试类中创建了四个任务,由此可见,创建了有限个线程池

5. 自定义线程池

分析上面案例可知,利用Executors工具类创建线程池虽然方便,但不够灵活。当提交的任务较多时,任务的执行需要排队,无法定义队伍的长度,这时我们可以创建线程池对象,从而自行修改自己想要修改的参数

利用Executors类创建线程池对象的方法源码如下,共有七个参数
在这里插入图片描述
借助API帮助文档,查看ThreadPoolExecutor的构造方法的参数说明
在这里插入图片描述

参数说明类型
corePoolSize核心线程数量(不能小于0)int
maximumPoolSize线程池中最大线程的数量(最大数量 >= 核心线程数量)int
keepAliveTime空闲时间(值) (不能小于0)long
unit空闲时间(单位) (用TimeUnitt指定)TimeUnit
workQueue阻塞队列(不能为null)BlockingQueue
threadFactory创建线程的方式(不能为null)ThreadFactory
handler要执行的任务过多时的解决方案(不能为null)RejectedExecutionHandler
5.1 情况一

提交任务量 = 核心线程数

现有一个线程池,现有如下规定,

  • 核心线程(corePoolSize):3
  • 临时线程:3
  • 提交 3 个任务

结果:线程池创建三个线程,分别去处理这三个任务
在这里插入图片描述

5.2 情况二

提交任务量 > 核心线程数

现有一个线程池,现有如下规定,

  • 核心线程(corePoolSize):3
  • 临时线程:3
  • 提交 5 个任务

结果:由于核心线程数为 3,只能分配三个线程去完成其中的三个任务,剩余的两个任务则会被放到阻塞队列中进行排队
在这里插入图片描述

5.3 情况三

提交任务量 > 核心线程数 + 阻塞队列长度

现有一个线程池,现有如下规定,

  • 核心线程(corePoolSize):3
  • 临时线程:3
  • 阻塞队列长度:3
  • 提交 8 个任务

结果:共提交了八个任务,线程池先创建三个线程处理其中三个任务,再将多余的三个线程放入阻塞队列中进行排队,发现还多出两个线程,线程池会再创建两个临时线程处理给剩下两个任务

注:任务的执行顺序不一定按照提交的顺序执行(先去服务队列外的)

临时线程创建的条件:

  • 核心线程都在忙
  • 阻塞队列已经排满了任务
    在这里插入图片描述
5.3 情况四

提交任务量 > 核心线程数 + 阻塞队列长度 + 临时线程数

现有一个线程池,现有如下规定,

  • 核心线程(corePoolSize):3
  • 临时线程:3
  • 阻塞队列长度:3
  • 提交 10 个任务

结果:除去已经分配的三个核心线程,三个临时线程以及阻塞队列中的三个线程,剩余的任务则会触发任务拒绝策略,也就是舍弃不要
在这里插入图片描述

任务拒绝策略说明
ThreadPoolExecutor.AbortPolicy默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常这是不推荐的做法
ThreadPoolExecutor.DiscardoldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicv调用任务的run()方法绕过线程池直接执行

任务解决策略是ThreadPoolExecutor中的内部类,其单独存在无意义,需依附于ThreadPoolExecutor,因此被设置为内部类

创建静态内部类:new 外部类.内部类()

以上任务拒绝策略了解即可

ThreadPoolExecutor.DiscardoldestPolicy 抛弃队列中排在第一个的任务,情况四中,则会抛弃任务四

// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    3, // /核心线程数量,能小于0
    6, // 最大线程数,不能小于0,最大数量 >= 核心线程数量
    60, // 空闲线程最大存活时间
    TimeUnit.SECONDS, // 时间单位
    new ArrayBlockingQueue<>(3), // 任务队列
    Executors.defaultThreadFactory(), // 创建线程工厂
    new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);

小结:

  1. 1.创建一个空的池子
  2. 2.有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

不断的提交任务,会有以下三个临界点

  • 当核心线程满时,再提交任务就会排队
  • 当核心线程满,队伍满时,会创建临时线程
  • 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值