线程池

发布时间 2023-03-24 14:38:18作者: 七分酷

线程池

线程池概述

线程池见名知意,就是指一个装多个线程的池子。

为什么需要线程池

在没有线程池的情况下,我们执行一个任务会创建一个线程,执行完毕后线程就会销毁,如果有新的任务就需要重复这些步骤,所以线程池存在的意义就是在执行完一个任务之后,线程不会销毁,并保存在线程池里面,如果有新的任务直接调用线程池里面的线程即可。

线程池的优点:
  1. 降低资源的消耗:通过重复利用现有的线程对象执行任务,避免了多次创建和销毁的步骤
  2. 提高了相应速度:因为省去了创建线程的步骤
  3. 提供附加功能:线程池的扩展性可以使得我们以后加入一些自己的功能,比如说定时,延时
创建步骤
  1. 创建线程池对象
  2. 有任务需要线程对象执行的时候,创建线程对象去执行,执行完毕后不销毁,存到线程池中
  3. 当所有任务都执行完毕,线程池就没有存在的必要了,所以需要关闭线程池

线程池实现代码

方法一:static Executors.newCachedThreadPool() 创建一个默认的线程池

public class ExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * Executors用于创建线程池
         * ExecutorService用于操作线程池
         */
        ExecutorService es=Executors.newCachedThreadPool();

        es.submit(new Runnable() {
            @Override
            public void run() {
                //任务1
                System.out.println(Thread.currentThread().getName()+"线程1");
            }
        });
        //任务2
        es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});

        //关闭线程池
        es.shutdown();
    }
}

结果是

image-20230322113404740

但是这造成一个问题,以上程序并没有体现出一个线程重复利用的思想,而是创建了两个线程执行两个任务,原因是:线程池遇到任务1时,创建了线程1执行,但是,任务1还没执行完,线程没有归还的时候,任务2抢占到了执行权,这个时候线程池没有多余的线程可以用,因此创建了线程2来执行任务2。

为了避免这个问题,需要让程序沉睡一下,让线程1有充足的时间执行完任务并归还

代码如下:

public class ExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * Executors用于创建线程池
         * ExecutorService用于操作线程池
         */
        ExecutorService es=Executors.newCachedThreadPool();

        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程1");
            }
        });
        //增加睡眠
        Thread.sleep(100);
        es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});

        //关闭线程池
        es.shutdown();
    }
}

结果:

image-20230322114042440

以上就实现了一个线程执行多个任务的案例。

方法二:static Executors.newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

代码如下:

public class ExecutorDemo2 {
    public static void main(String[] args) throws InterruptedException {
        /**
         * newFixedThreadPool中的参数表示线程池中的最大线程数
         */
        ExecutorService es = Executors.newFixedThreadPool(10);

        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程1");
            }
        });
        Thread.sleep(100);
        es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});

        //关闭线程池
        es.shutdown();
    }
}

image-20230322115126580

可以发现即使让程序进入睡眠仍然创建了两个线程,这是因为newFixedThreadPool()方法要求,在创建完最大数量的线程数之前,只会新建线程,而不会重复利用旧线程

方式三:自定义线程池

进入newCachedThreadPool() 可以发现方法内部也是通过调用ThreadPoolExecutor类来创建线程池的

image-20230322125802931

因此可以直接根据该类自定义线程池

image-20230322125914958

以上形参一共有7个,可以举例来说明这些参数的作用

案例:假如现在有一家高级餐厅(线程池),实行员工顾客一对一服务,那么代表每有一名顾客,餐厅就需要招聘一名员工为其服务,但是老板基于成本考虑,餐厅不能无限制的招人,因为当顾客离去后,招聘的人太多了就浪费了成本,因此老板把员工分成了正式员工和临时员工,正式员工会一直呆在餐厅工作,而临时员工只会在正式员工繁忙的时候被招进来工作,当顾客较少时,临时员工会被辞退,另外,即便有临时员工的存在,一家餐厅的同时服务人数也是有上限的,等待被服务的顾客,只能在餐厅外排队等候,同时餐厅还对排队人数做了限制,如果排队人数过多的话,超出范围的人数会被拒绝服务。

现在根据本案例中提及的内容,和ThreadPoolExecutor构造方法的形参对号入座

形参名 事件解释 术语解释
int corePoolSize 正式员工的数量 核心线程数
int maximumPoolSize 餐厅最大的员工数量 最多线程数
long keepAliveTime 临时员工空闲多长时间会被辞退(值) 临时线程数空闲时长
TimeUnit unit 临时员工空闲多长时间会被辞退(单位) 临时线程数空闲时长(单位)
BlockingQueue workQueue 排队的客户 阻塞队列
ThreadFactory threadFactory 员工从哪里招 线程创建处
RejectedExecutionHandler handler 排队人数过多时拒绝服务 定义拒绝策略

代码实现:

public class ExecutorDemo3 {
    public static void main(String[] args) throws InterruptedException {
		//创建自定义线程池
        ThreadPoolExecutor te = new ThreadPoolExecutor(
                2,
                4,
                2,
                TimeUnit.DAYS,
                new ArrayBlockingQueue<>(6),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        for (int i = 0; i < 10; i++) {
            te.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "正在执行");
            });
        }
        //关闭线程池
        te.shutdown();
    }
}

结果:

image-20230322131955897

可以看到正式员工和临时员工都被调用了,但是服务人数最多为10人,因为服务人数上限=最大员工数+排队人数,如果超过这个数量会报错,这是因为超出的人员会被拒绝服务。

image-20230322132135704

拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略结构

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