啊!线程池原来也不是想象中那么可怕

前言

当初熊某第一次接触数据库连接池的时候,简直是一脸懵逼!(令我想起一次在惠州罗浮山的天然游泳池上游泳的经历)连接对象我知道,还会有连接池?!生活中的一些事物都可以用到代码中吗?!顿时觉得我自己原来是那么渺小。
在这里插入图片描述
后来得知是提前创建预先约定好的连接对象,然后放到一个容器上(可以是数组、队列或者其它),这样就能减少连接创建时间。

那线程池也是同样的道理,在比较简单的程序下,你直接new Thread()也不会有什么大问题,毕竟在简单业务情况,创建和销毁线程的微小变化可以小到你都察觉不出来

可惜啊,在二十一世纪二十年代(简称2020年)的现在,如果业务情况负责的情况下,直接new Thread()带来的资源损耗后果都直接可以让你宕机。

首先创建和销毁线程又是个大工程,毕竟线程也需要内存空间。大量线程创建都可以搞得你的程序报出OOM(Out of memory)的幸福红字。

其次大量线程回收也会影响GC回收时间。

再次你创建一个线程要5s,执行只需要2s,执行时间少于创建时间,感觉有点得不偿失啊。

不过,重点来了!
在这里插入图片描述
我们可以模仿连接池的设计思路,自己来实现一个线程池,这样就能避免以上的麻烦!

设计思路

相信各位(包括我)都已经蠢蠢欲动,不过在开始前还是要整理下思路:

  1. 我们需要一个队列,用于存放已创建的线程,这里用阻塞队列
  2. 在创建线程池的时候应该要设定边界,无界或者边界特别大的线程池容易造成OOM。
  3. 需要一个任务提交方法,当提交任务后,空闲线程会自动执行,否则会在阻塞队列中等待。

实现环节

好了,接下来上代码!

public class MyThreadPool {
   // 阻塞队列
   private BlockingQueue<Runnable> workQueue;
   // 用于存放已经创建的工作线程对象
   private List<ExecuteThread> executeThreadList = new ArrayList<>();

   // 创建线程池时需要设定创建的工作线程线程数(pool)大小
   public MyThreadPool(int pool, BlockingQueue<Runnable> queue){
       this.workQueue = queue;
       for (int i = 0; i < pool; i++){
           ExecuteThread executeThread = new ExecuteThread();
           executeThread.start();
           executeThreadList.add(executeThread);
       }
   }

   // 提交任务
   public void execute(Runnable command){
       workQueue.add(command);
   }

   // 创建一个内部类作为工作线程类
   class ExecuteThread extends Thread{
       @Override
       public void run() {
           // 循环取出任务并执行
           while (true){
               try {
                   // 当调用take方法时,任务对象会自动出列
                   Runnable task = workQueue.take();
                   task.run();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
   }
}

是不是很简单?接下来我们写个测试demo看看怎么样:

public static void main(String[] args) {
        MyThreadPool myThreadPool = new MyThreadPool(5, new ArrayBlockingQueue<>(2));
        myThreadPool.execute(() -> {
            System.out.println("熊小哥好帅啊");
        });
    }

结果就和现实一样,非常符合:
在这里插入图片描述
代码如果理解的不太顺的话可以回去研究下。关于阻塞队列方面的知识我这里就不说了,因为还有更重要的要说,就是生产者-消费者模式
在这里插入图片描述

什么是生产者-消费者模式

生产者-消费者模式一个有三个角色:生产者、缓存区、消费者。(图片网上找的)
在这里插入图片描述
产生数据的一方就是生产者,将产生的数据进行处理的一方就是消费者。

但是因为可能有时候生产者的效率不高,一分钟只能产出一条数据,而消费者可以一分钟处理十条数据,如果每次生产者产生一条数据,消费者就处理一条数据,这种方式可以说很低效,那么就引入了缓存区这一角色,生产者产生的数据放到缓存区,10分钟后消费者发现有10条数据,就进行处理,这之前消费者该干嘛就干嘛。这就是常说的批处理。同时生产者和消费者之间不需要相互调用,达到一个解耦的作用。

简单点说就是生产者就是产生数据然后把数据放到缓存区中,消费者则将缓存区的数据取出来进行处理,缓存区相当于一个中间点的角色。

了解这一设计模式可以加深我们对阻塞队列的认知,用上述代码来说明,首先线程池本身就是消费者,因为它是负责处理数据的:

// 负责处理数据,也就是消费阻塞队列上的任务,那么阻塞队列就相当于上面的缓存区这一角色
class ExecuteThread extends Thread{
        @Override
        public void run() {
            // 循环取出任务并执行
            while (true){
                try {
                    // 当调用take方法时,任务对象会自动出列
                    Runnable task = workQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

我们使用线程池的地方就是生产者,生产后的数据交给线程池去处理,这里的生产者指的就是上面的测试demo代码:

public static void main(String[] args) {
        MyThreadPool myThreadPool = new MyThreadPool(5, new ArrayBlockingQueue<>(2));
        myThreadPool.execute(
                () -> {
                    // 生产数据
                    System.out.println("熊小哥好帅啊");
                });
    }

结尾

今天的聊天差不多结束了,总结一下就是深入理解生产者-消费者模式可以更有助于我们对线程池的理解,我这里还有一些Java中的线程池没有介绍,各位看官感兴趣可以搜索看下。

好了,告辞告辞!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值