多线程之线程池

目录

[1 线程池是什么](#1 线程池是什么)

[2 Java标准库中的线程池](#2 Java标准库中的线程池)

[3 自己实现一个固定线程数的线程池](#3 自己实现一个固定线程数的线程池)


1 线程池是什么

在多线程编程中,每当需要一个线程的时候,我们就要进行创建线程的操作。但是这样频繁创建销毁线程的操作是很消耗资源的。所以,我们有了"线程池"这个东西,线程池就是先把线程创建好放在"池子"里,等到我们需要使用线程的时候直接从里面拿就好了,并且在用完之后,将线程再重新放入这个"池子"中。

所以,线程池最大的好处就是减少了每次创建和销毁线程的资源损耗。

2 Java标准库中的线程池

Java标准库中的线程池是ThreadPoolExcutor。

线程池的工作原理就是,先创建一个线程池,然后通过submit方法(submit方法中的是一个Runnable,这里使用的是匿名类的方法来创建对象)给任务队列提交一个任务,这里的任务队列其实就是阻塞队列,把这个任务提交之后,通过阻塞队列安排线程进行执行这个任务。

ThreadPoolExcutor有很多构造方法,这里我们选择参数最多的一种来进行介绍:

java 复制代码
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize:核心线程,就是一个线程池中至少要包含的线程个数。线程池创建的时候这么多数量的线程就会随之创建,等到线程池销毁的时候,这些线程才全部销毁。
  • maximumPoolSize:最大线程数,一个线程池中包含了核心线程和非核心线程,其中非核心线程是自适应的,也就是说如果提交给线程池的任务多,那么这些线程就存在,如果提交给线程池任务少,那么这些线程就销毁。
  • keepAliveTime:是对于非核心线程在没有处理任务的时候能够存活的时长。
  • unit:这是一个枚举类型,其中的内容是线程存活时长的单位。
  • workQueue:阻塞队列,也就是前面我们提到的任务队列。
  • threadFactory:工厂模式,与单例模式相同也是一种设计模式,但是工厂模式是统一的构造并初始化线程的。
  • handler:拒绝策略,在线程池中,如果任务队列满了,事实上不会真的进行阻塞,而是执行拒绝策略的相关代码,拒绝策略又有好多种:
    • AbortPolicy:超出负荷,直接抛出异常
    • CallerRunPolicy:让调用submit方法的线程执行本次任务
    • DiscardOldestPolicy:丢弃队列中最老的任务,来执行本次任务
    • DiscardPolicy:直接丢弃本次任务

为了简化线程池的使用,使用Excutors来对ThreadPoolExcutor进行封装。

Excutors创建线程池有四种方式:

  • newFixedThreadPool:创建固定线程数的线程池(核心线程数和最大线程数相等)
  • newCachedThreadPool:创建线程数目动态增长的线程池(最大线程数是一个很大的数,能够一直增长)
  • newSingleThreadExecutor:创建只包含单个线程的线程池
  • newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令

示例:

java 复制代码
public static void main(String[] args) {
    ExecutorService pool = Executors.newCachedThreadPool();
    pool.submit(()-> {
        @Override
        public void run() {
            System.out.println("你好");
        }
    });
}

但是此处存在一个问题就是,在线程把任务都执行结束之后,程序并不会结束。

示例:

java 复制代码
public static void main(String[] args) {
    ExecutorService pool = Executors.newCachedThreadPool();
    for (int i = 0; i < 100; i++) {
        int n = i;
        pool.submit(()->{
            System.out.println(n);
        });
    }
//        pool.shutdown();
//        pool.awaitTermination(3, TimeUnit.SECONDS);
}

// 当任务结束之后,程序并不会结束,手动结束之后,控制台返回:
// Process finished with exit code 130
// 这说明程序不是自动结束的
// 程序不会结束的原因是,虽然任务已经处理结束,但是阻塞队列中的take还在阻塞等待新的任务(线程池中的线程是前台线程,会阻碍程序结束)
// 所以这个时候需要shutdown方法来关闭线程池 但是这个方法不能保证需要执行的任务全部执行完毕
// 如果要等任务执行完毕之后再进行关闭,那么就使用awaitTermination方法
// awaitTermination方法的返回值为boolean类型,第一个参数表示等待时长,第二个参数表示等待时长的单位

3 自己实现一个固定线程数的线程池

java 复制代码
/**
 * 实现一个固定线程数的线程池
 * 在这个代码中,其实只需要实现一个 submit 方法,和 创建一个固定数量线程的方法(创建固定数量的线程在构造方法中进行)
 */
class MyThreadPool{

    // 因为要提交的任务是 Runnable 所以这里<>中是Runnable
    private BlockingQueue<Runnable> queue = null;

    // n是这个线程池的线程个数
    // 在构造方法中创建线程,保证在创建线程池的时候就创建线程了
    public MyThreadPool(int n){
        queue = new ArrayBlockingQueue<>(1000);

        // 创建 n 个线程
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(()->{
                   try {
                       // 创建线程
                       while(true) {
                           // submit 之后先把任务放到 queue 中
                           // 所以要从 queue 中取出任务
                           Runnable task = queue.take();
                           task.run();
                       }
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
            });
            // 创建好线程之后,启动线程
            t.start();
        }
    }
    // 这里的 submit 方法就起到一个把任务放到阻塞队列中的一个效果
    public void submit(Runnable task) throws InterruptedException {
        queue.put(task);
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(5);
        for (int i = 0; i < 10; i++) {
            pool.submit(()->{
                System.out.println("你好");
            });
        }
    }
}
相关推荐
洛克大航海3 小时前
7-SpringCloud-服务网关 Gateway-高级特性 Route
java·spring cloud·gateway·route
Dreams_l3 小时前
redis中的数据类型
java·开发语言
梵得儿SHI3 小时前
Java IO 流详解:字符流(Reader/Writer)与字符编码那些事
java·开发语言·字符编码·工作原理·字符流·处理文本
盖世英雄酱581363 小时前
java深度调试【第三章内存分析和堆内存设置】
java·后端
爬山算法4 小时前
Redis(84)如何解决Redis的缓存击穿问题?
java·redis·缓存
程序定小飞4 小时前
基于springboot的电影评论网站系统设计与实现
java·spring boot·后端
一 乐4 小时前
汽车销售|汽车推荐|基于SprinBoot+vue的新能源汽车个性化推荐系统(源码+数据库+文档)
java·数据库·vue.js·汽车·毕设·汽车个性化推荐
虾说羊4 小时前
最细Maven教程以及Maven私服搭建
java·数据库·maven
杯莫停丶4 小时前
设计模式之:装饰器模式
java·设计模式·装饰器模式