聊一聊java线程池ThreadPoolExecutor(一)

关于java线程池,大家手写的可能不多,使用的话一般都是直接使用封装好的,具体内部的实现可能只是简单的了解下,一来是方便使用java线程池,当然还有一个更重要的作用,嘿嘿,大家都懂的。

其实理解java中一些源码的运行机制对大家的学习帮助是非常大的,一方面源码中的代码都十分精简,逻辑之间的管理很优雅,另一方面,熟悉源码之后,遇到现实生产中的问题,就很容易去关联到可能出现问题的某个节点,不然真拉出一大堆日志,然后一点点排查重试,特别是并发问题,真的能把人逼的崩溃哈。

今天就来说下线程池,我们由浅入深,一点点来哈,先贴一个构造方法,介绍几个关键的属性。线程池的关键ThreadPoolExecutor,大家尽量记住这个单词哈,不然每天都说线程池,结果不知道是哪个类,真被问到了,太尬了哈。看下构造方法

  public ThreadPoolExecutor(int corePoolSize,     //核心线程数
                              int maximumPoolSize,  //最大线程数
                              long keepAliveTime,   //存活时间
                              TimeUnit unit,        //存活时间单位
                              BlockingQueue<Runnable> workQueue,    //缓存队列
                              ThreadFactory threadFactory,         //线程工厂
                              RejectedExecutionHandler handler     //拒绝策略
							  ) {
        if (corePoolSize < 0 ||maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池的构造方法几乎最终都是指向这个构造方法的。这个构造方法也就是对几个属性进行了限制,然后赋值,很好理解,我们分别来看下各个属性。

  • corePoolSize:核心线程数,说到线程池,大家肯定要对线程池有个概念,这是一个存放多个线程的地方,好似一个池子一样,你需要的时候,就取出一个线程进行使用。因此,这个池子中肯定是要有线程的。当然,线程开多了,肯定会消耗服务器的性能,占用大量的资源。所以线程池在设计的时候就制定了设置线程池的策略。corePoolSize就是这种策略的一个参数。它的含义就是线程池一直会开着的线程,当然,也可以为0。不过最好不要,既然开了线程池,说明业务量并不小,线程的频繁创建与销毁也是要消耗资源的。
  • maximumPoolSize:最大线程数,既然是一个池子,存放的数量肯定是有限制的,不然你传过来1千万数据,难道直接给你开1千万线程?别逗了,那样服务器早挂了~这里的最大线程数,就是一个限制,哪怕你传来的数据再多,一旦线程达到了最大线程数,就不允许在继续开线程了。当然这个参数不能设为0,不然你这个线程池还有啥意义?当然maximumPoolSize>corePoolSize是必须的。
  • keepAliveTime 线程存活时间,举个例子,corePoolSize=2,maximumPoolSize=3,这个时候刚好没有任务需要线程池执行了,那么多余的一个线程就可以根据keepAliveTime 进行关闭了
  • unit  时间单位,比如keepAliveTime是3,unit是TimeUnit.HOURS,这就表示3小时后关闭,unit是TimeUnit.SECONDS,那就表示3秒了。
  • workQueue 缓存队列,刚才说了,线程最好不要一直开,那么需要执行的认为有好几千条怎么办?这个时候就要把需要执行的参数扔进缓存队列了,然后当有线程空闲下来的时候,就到缓存队列里面去取数据,这样就友好许多了。
  • threadFactory  线程工厂
  • handler  拒绝策略。  这两个后面再说。

 好了,上面就是线程池最基本的参数了,不过要维持一个线程池高效运转,内部自然有一套自己的机制的。我们先从最近本的看起,看下这三个参数:corePoolSize,maximumPoolSize,workQueue。这三个参数是有明显的关系的,比如,线程多了,workQueue明显就可以少放点数据,还有什么时候放数据到workQueue,什么时候开线程?等等。

直接上代码,大家看的更清楚一些。

public class ThreadPoolTest {

    public static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) throws Exception {
        // 创建一个核心线程为2,最大线程为8,缓存队列为10的线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 8, 0, TimeUnit.SECONDS, workQueue);
        // 拒绝策略先不说,i最大值写小点
        for (int i = 0; i < 18; i++) {
            poolExecutor.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                    //睡眠下,如果线程运行太快,直接空出来了,就没法试验了
                    Thread.sleep(200);
                }
            });
        }
         // 关闭下线程池,不能让它一直跑着哈
         poolExecutor.shutdown();
    }

我们建立了最大线程为8,缓存队列为10的线程池,同时塞进去18个任务,结果可以看到,启动了总共启动了8个线程,可以说是每个线程都用到了。

线程名:pool-1-thread-1
线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-5
线程名:pool-1-thread-4
线程名:pool-1-thread-6
线程名:pool-1-thread-7
线程名:pool-1-thread-8
线程名:pool-1-thread-5
线程名:pool-1-thread-8
线程名:pool-1-thread-1
线程名:pool-1-thread-7
线程名:pool-1-thread-4
线程名:pool-1-thread-3
线程名:pool-1-thread-6
线程名:pool-1-thread-2
线程名:pool-1-thread-7
线程名:pool-1-thread-1

但是如果我们把任务改成17个呢?

for (int i = 0; i < 17; i++) {
            poolExecutor.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("线程名:" + Thread.currentThread().getName());
                    //睡眠下,如果线程运行太快,直接空出来了,就没法试验了
                    Thread.sleep(200);
                }
            });
        }

就会发现,只启动了7个线程哦

线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-6
线程名:pool-1-thread-7
线程名:pool-1-thread-1
线程名:pool-1-thread-5
线程名:pool-1-thread-4
线程名:pool-1-thread-3
线程名:pool-1-thread-2
线程名:pool-1-thread-4
线程名:pool-1-thread-5
线程名:pool-1-thread-1
线程名:pool-1-thread-7
线程名:pool-1-thread-6
线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-7

核心线程肯定是启动了的,然后来了17个任务,线程开了7个,缓存队列10个,伙伴们是不是发现了什么?线程本来可以创建8个的,然后缓存队列放9个数据,可是现在是只创建了7个线程,缓存队列放了10个数据。很明显,线程池的运行或者说使用顺序是先coreSize > workQueue > maximumPoolSize,只有核心线程已经被占用,workQueue放满了之后,才会再次开启线程进行处理数据,直到开启到最大线程数。

那如果任务多了怎么办?比如来了100个任务。那当然是抛出异常啦,默认的拒绝策略是这么做的,当然你也可以自己定义拒绝策略,这个后面再说哈。

扫描二维码关注公众号,回复: 13036060 查看本文章

今天就先说到这了,有点简单,大家瞄一眼就好,莫要见笑哈。

No sacrifice,no victory~

猜你喜欢

转载自blog.csdn.net/zsah2011/article/details/110187618
今日推荐