多线程复习总结之线程池的创建及使用

0、引言

在面试中,线程池是其中的一大考点。本篇博客则记录关于线程池的一些总结,包括线程池的使用原因、创建线程池、线程池的形式及线程池的工作流程,希望能对你更好的理解线程有帮助。

1、为什么要创建线程池?

线程池是一种池化技术,目的是避免线程频繁的创建和销毁带来的性能消耗。它是把已创建的线程放入“池”中,当有任务来临时就可以重用已有的线程,无需等待创建的过程,这样就可以有效提高程序的响应速度。

《Java 并发编程艺术》一书中提到使用线程池的好处:

1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的资源浪费。 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。
2、方便管理线程:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以对线程进行统一的分配,优化及监控。

2、如何创建线程池?

Java 中创建线程池有以下两种方式

1、通过 ThreadPoolExecutor 类创建(推荐)
2、通过 Executors 类创建

2.1 Executors 类创建线程池

源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过查看源码,我们发现:

1、这两种方式在本质上是一种方式,都是通过 ThreadPoolExecutor 类的方式创建,因为 Exexutors 类调用了 ThreadPoolExecutor 类的方法。
2、其中,上面的三张图片代表着Executors 类创建线程池的三种常用形式:固定线程池、独立线程池和缓冲线程池

实例

    public static void main(String[] args) {
        /**
         * 创建固定线程池(大小固定)
         **/
        ExecutorService pool1 = Executors.newFixedThreadPool(2);
        Runnable task1 = () -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
//        pool1.execute(task1);
//        pool1.execute(task1);
//        pool1.execute(task1);

        /**
         * 创建缓冲线程池(大小可变)
         **/
        ExecutorService pool2 = Executors.newCachedThreadPool();
        Runnable task2 = () -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
//        pool2.execute(task2);
//        pool2.execute(task2);
//        pool2.execute(task2);

        /**
         * 创建独立线程池(线程相互独立)
         **/
        ExecutorService pool3 = Executors.newSingleThreadExecutor();
        Runnable task3 = () -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        };
        pool3.execute(task3);
        pool3.execute(task3);
        pool3.execute(task3);
    }

在这里插入图片描述

2.2 ThreadPoolExecutor 类创建线程池

源码:
在这里插入图片描述
实例:

public class Thread {
    /**
     * corePoolSize:线程池中所保存的核心线程数,包括空闲线程
     * maximumPoolSize:池中允许的最大线程数。
     * keepAliveTime:线程池中的空闲线程所能持续的最长时间,
     *      当线程池中的线程数量小于 corePoolSize 时,
     *      如果里面有线程的空闲时间超过了 keepAliveTime,
     *      就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
     * unit:持续时间的单位。
     * workQueue:任务执行前保存任务的队列,仅保存由 execute 方法提交的 Runnable 任务。
     **/
    public static void main(String[] args) {

        //创建ThreadPoolExecutor线程池对象,并初始化该对象的各种参数
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 2000, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(5));

        //往线程池中循环提交线程
        for (int i = 0; i < 15; i++) {
            //创建线程类对象
            MyTask myTask = new MyTask(i);
            //开启线程
            executor.execute(myTask);
            //获取线程池中线程的相应参数
            System.out.println("线程池中线程数目:" +executor.getPoolSize() + ",队列中等待执行的任务数目:"+executor.getQueue().size() + ",已执行完的任务数目:"+executor.getCompletedTaskCount());
        }
        //待线程池以及缓存队列中所有的线程任务完成后关闭线程池。
        executor.shutdown();
    }
}
class MyTask implements Runnable {
    private int num;

    public MyTask(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println("正在执行task " + num );
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task " + num + "执行完毕");
    }

    /**
     * 获取(未来时间戳-当前时间戳)的差值,
     * 也即是:(每个线程的睡醒时间戳-每个线程的入睡时间戳)
     * 作用:用于实现多线程高并发
     * @return
     * @throws ParseException
     */
    public long getDelta() throws ParseException {
        //获取当前时间戳
        long t1 = System.currentTimeMillis();
        //获取未来某个时间戳(自定义,可写入配置文件)
        String str = "2016-11-11 15:15:15";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long t2 = simpleDateFormat.parse(str).getTime();
        return t2 - t1;
    }

2.3 为什么不推荐Executors 类创建线程池?

在阿里巴巴java开发手册中明确规定不允许使用Executors创建线程池:
在这里插入图片描述
这是什么意思呢?以固定线程池为例:
在这里插入图片描述

java中的阻塞队列有两种:
1、ArrayBlockingQueue是一个以数组设计的有界队列,必须设置大小
2、LinkedBlockingQueue 是一个以链表实现的有界阻塞队列,容量可以选择设置,不设置的话是无界的最大长度为 Integer.MAX_VALUE

可以看到实现是实例化一个ThreadPoolExecutor,创建的队列为 LinkedBlockQueue。
1、由于Executors创建线程池没有传入阻塞队列的长度,阻塞队列就是一个无边界队列。
2、对于一个无边界队列来说是可以向其中无限添加任务的,这种情况下可能由于任务数太多而导致内存溢出。

3、线程池有哪些形式

在了解线程池之前,先来了解一下相关参数:

1、corePoolSize:线程池中所保存的核心线程数,包括空闲线程
2、maximumPoolSize:池中允许的最大线程数。
3、keepAliveTime:线程池中的空闲线程所能持续的最长时间,当线程池中的线程数量小于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。
4、unit:持续时间的单位。
5、workQueue:任务执行前保存任务的队列,仅保存由 execute 方法提交的 Runnable 任务。

线程池的种类:

01、CachedThreadPool(可缓存的线程池)

特点:

1、该线程池的核心线程数量是0,线程的数量最高可以达到Integer 类型最大值;
2、创建ThreadPoolExecutor实例时传过去的参数是一个SynchronousQueue实例,说明在创建任务时,若存在空闲线程就复用它,没有的话再新建线程。
3、线程处于闲置状态超过60s的话,就会被销毁。

实例:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService cachedThreadPool=new ThreadPoolExecutor(0,Integer.MAX_VALUE,60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
        int index=0;
        for (int i = 0; i < 10; i++) {
            ++index;
            Thread.sleep(index*1000);
            int finalIndex = index;
            cachedThreadPool.execute(() -> {
                System.out.println(Thread.currentThread()+":该线程执行了"+ finalIndex*1000+"ms");
            });
        }
        cachedThreadPool.shutdown();
    }
02、FixedThreadPool(定长线程池)

特点:

1、线程池的最大线程数等于核心线程数,并且线程池的线程不会因为闲置超时被销毁。
2、使用的列队是LinkedBlockingQueue,表示如果当前线程数小于核心线程数,那么即使有空闲线程也不会复用线程去执行任务,而是创建新的线程去执行任务。如果当前执行任务数量大于核心线程数,此时再提交任务就在队列中等待,直到有可用线程。

实例:

public static void main(String[] args) throws InterruptedException {
    ExecutorService fixedThreadPool =new ThreadPoolExecutor(3,3,0, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(2));
    int index=0;
    for (int i = 0; i < 10; i++) {
        ++index;
        Thread.sleep(index*1000);
        int finalIndex = index;
        fixedThreadPool.execute(() -> {
            System.out.println(Thread.currentThread()+":该线程执行了"+ finalIndex*1000+"ms");
        });
    }
    fixedThreadPool.shutdown();
}
03、SingleThreadExecutor(单线程线程池)

特点

该线程池本质上就是只有一个线程数的newFixedThreadPool,它只有一个线程在工作,所有任务按照指定顺序执行。就不贴代码了

04、ScheduledThreadPool(支持定时的定长线程池)

特点:

newScheduledThreadPool的方法不是直接返回一个ThreadPoolExecutor实例,而是通过有定时功能的ThreadPoolExecutor,也就是ScheduledThreadPoolExecutor 来返回ThreadPoolExecutor实例,从源码中可以看出:
1、该线程池可以设置核心线程数量,最大线程数与newCachedThreadPool一样,都是Integer.MAX_VALUE。
2、该线程池采用的队列是DelayedWorkQueue,具有延迟和定时的作用。
在这里插入图片描述

实例:

public static void main(String[] args) {
    ExecutorService scheduledThreadPool=new ScheduledThreadPoolExecutor(3);
    ((ScheduledThreadPoolExecutor) scheduledThreadPool).schedule(() -> System.out.println("=====>延迟3秒"),3,TimeUnit.SECONDS);
    ((ScheduledThreadPoolExecutor) scheduledThreadPool).scheduleAtFixedRate(() -> System.out.println("=====>执行中"),1,2,TimeUnit.SECONDS);
}

4、线程池的工作流程是怎样的?

1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

2、当执行 execute() 方法添加一个任务时,线程池会判断:
(a) 若正在运行的线程数量小于 corePoolSize 值,则立刻创建线程运行此任务;
(b) 若正在运行的线程数量大于或等于 corePoolSize 值,则将此任务放入队列;
© 若此时队列满了,而且正在运行的线程数量小于 maximumPoolSize 值,则创建非核心线程立刻运行这个任务;
(d) 若队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize 值,那么线程池会执行设置的拒绝策略。

3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

4、当一个线程空闲时,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize 值,那么这个线程会被销毁。

在这里插入图片描述

5、总结

肝文不易,如果对你有帮助,给个赞呗!!!

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

--流星。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值