并发(个人收集)

浅谈线程池

2019-08-21  本文已影响0人  程序员阿兵

1. 简介

在互联网的开发场景下,很多业务场景下我们需要使用到多线程的技术,从 Java 5 开始,Java 提供了自己的线程池,线程池就是一个线程的容器,每次只执行额定数量的线程。java.util.concurrent包中提供了ThreadPoolExecutor类来管理线程,本文将介绍一下ThreadPoolExecutor类的使用。


2.为什么要使用线程池?

在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:


3.线程池使用方式

java.util.concurrent包中提供了多种线程池的创建方式,我们可以直接使用ThreadPoolExecutor类直接创建一个线程池,也可以使用Executors类创建,下面我们分别说一下这几种创建的方式。


4.Executors创建线程池

Executors类是java.util.concurrent提供的一个创建线程池的工厂类,使用该类可以方便的创建线程池,此类提供的几种方法,支持创建四种类型的线程池,分别是:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。


4.1 newCachedThreadPool

创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。

     ExecutorService executorService = Executors.newCachedThreadPool();
        
        for (int i = 0; i < 20; i++) { // 循环第二次 闲置60s, 复用上一个任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("当前线程,执行耗时任务,线程是:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

上面的创建了一个无边界限制的线程池,下面可以看到执行结果。在线程池无限大的情况下,当前可以运行的线程数量就不会收到限制,也就是创建多少线程即执行多少,下面的结果中有显示15条线程

当前线程,执行耗时任务,线程是:pool-1-thread-2
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-3
当前线程,执行耗时任务,线程是:pool-1-thread-13
当前线程,执行耗时任务,线程是:pool-1-thread-6
当前线程,执行耗时任务,线程是:pool-1-thread-10
当前线程,执行耗时任务,线程是:pool-1-thread-12
当前线程,执行耗时任务,线程是:pool-1-thread-11
当前线程,执行耗时任务,线程是:pool-1-thread-9
当前线程,执行耗时任务,线程是:pool-1-thread-4
当前线程,执行耗时任务,线程是:pool-1-thread-5
当前线程,执行耗时任务,线程是:pool-1-thread-7
当前线程,执行耗时任务,线程是:pool-1-thread-8
当前线程,执行耗时任务,线程是:pool-1-thread-20
当前线程,执行耗时任务,线程是:pool-1-thread-18
当前线程,执行耗时任务,线程是:pool-1-thread-17
当前线程,执行耗时任务,线程是:pool-1-thread-16
当前线程,执行耗时任务,线程是:pool-1-thread-19
当前线程,执行耗时任务,线程是:pool-1-thread-15
当前线程,执行耗时任务,线程是:pool-1-thread-14

4.2 newFixedThreadPool

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。

      ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) { // 循环第二次 闲置60s, 复用上一个任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("当前线程,执行耗时任务,线程是:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

下面可以看到结果:一直限制在指定线程数量范围内,最大创建线程数5个

当前线程,执行耗时任务,线程是:pool-1-thread-5
当前线程,执行耗时任务,线程是:pool-1-thread-4
当前线程,执行耗时任务,线程是:pool-1-thread-3
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-2
当前线程,执行耗时任务,线程是:pool-1-thread-4
当前线程,执行耗时任务,线程是:pool-1-thread-3
当前线程,执行耗时任务,线程是:pool-1-thread-5
当前线程,执行耗时任务,线程是:pool-1-thread-2
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-4
当前线程,执行耗时任务,线程是:pool-1-thread-2
当前线程,执行耗时任务,线程是:pool-1-thread-3
当前线程,执行耗时任务,线程是:pool-1-thread-5
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-2
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-5
当前线程,执行耗时任务,线程是:pool-1-thread-3
当前线程,执行耗时任务,线程是:pool-1-thread-4

4.3 newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    /**
     * 创建给定延迟后运行命令或者定期地执行的线程池
     */
    public static void createScheduledThreadPool() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            final int currentIndex = i;
            //定时执行一次的任务,延迟1s后执行
            scheduledThreadPool.schedule(() -> {
                System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
                countDownLatch.countDown();
            }, 1, TimeUnit.SECONDS);
            //周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
            scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "every 3s"), 2, 3, TimeUnit.SECONDS);
        }
    }

这里创建了一个调度的线程池,执行两个任务,第一个任务延迟1秒后执行,第二个任务为周期性任务,延迟2秒后,每三秒执行一次

pool-1-thread-1, currentIndex is : 0
pool-1-thread-2, currentIndex is : 1
pool-1-thread-3, currentIndex is : 2
pool-1-thread-2, currentIndex is : 3
pool-1-thread-4, currentIndex is : 4
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-3every 3s
pool-1-thread-1every 3s
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-4every 3s
pool-1-thread-4every 3s
pool-1-thread-3every 3s
pool-1-thread-2every 3s

可以看到,第一个任务执行完毕后,开始执行定时调度型任务
该线程池提供了多个方法:

4.4 newSingleThreadExecutor

  ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 20; i++) { // 循环第二次 闲置60s, 复用上一个任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("当前线程,执行耗时任务,线程是:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

可以看到执行结果:当前只能存在一个线程任务

当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-1
当前线程,执行耗时任务,线程是:pool-1-thread-1
。
。

上面只会复用线程thread-1


5. 四种线程池对比

线程池方法 初始化线程池数 最大线程池数 时间单位 工作队列
newCachedThreadPool 0 Integer.MAX_VALUE 60秒 SynchronousQueue
newFixedThreadPool 入参指定大小 入参指定大小 0毫秒 LinkedBlockingQueue
newScheduledThreadPool 入参指定大小 Integer.MAX_VALUE 0微秒 DelayedWorkQueue
newSingleThreadExecutor 1 1 0毫秒 LinkedBlockingQueue

6.ThreadPoolExecutor创建线程池


        ThreadPoolExecutor executorService =
                new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());

        for (int i = 0; i < 20; i++) { // 循环第二次 闲置60s, 复用上一个任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("当前线程,执行耗时任务,线程是:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

各个参数含义:

在上述介绍的几种 Executors线程池 查看源码最终都是采用ThreadPoolExecutor
 指定线程池大小
public static ExecutorService newFixedThreadPool(int nThreads) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
  }

 public static ExecutorService newSingleThreadExecutor() {
      return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>()));
  }

 public static ExecutorService newCachedThreadPool() {
      return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
  }


参数三/四:时间数值keepAliveTime, 单位:时分秒 60s
只有在正在执行的任务Runnable > corePoolSize --> 参数三/参数四 才会起作用

Runnable1执行完毕后 闲置60s,如果过了闲置60s,会回收掉Runnable1任务,,如果在闲置时间60s 复用此线程Runnable

workQueue队列 :会把超出的任务加入到队列中 缓存起来

上一篇下一篇

猜你喜欢

热点阅读