线程池总结以及相关面试问题总结

一、什么是线程池?有什么作用?

  • 1、什么是线程池

java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

  • 2、线程池有什么好处

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。所以线程池有以下好处:

1、提高效率 创建好一定数量的线程放在池中,等需要使用的时候就
	从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
2、方便管理 可以编写线程池管理代码对池中的线程同一进行管理,
	比如说启动时有该程序创建100个线程,每当有请求的时候,就
	分配一个线程去工作,如果刚好并发有101个请求,那多出的这
	一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。

二、常见的线程池以及使用场景

创建线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5); 
ExecutorService cachedPool = Executors.newCachedThreadPool(); 
ExecutorService singlePool = Executors.newSingleThreadExecutor(); 
ExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
  • 1、newFixedThreadPool
    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  • 2、newCachedThreadPool
    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 3、newSingleThreadExecutor
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 4、newScheduledThreadPool
    创建一个定长线程池,支持定时及周期性任务执行。
  public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

--------注意!!!!
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

3、线程池的状态

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
状态摘抄于大神博客

线程池各个状态切换框架图:

在这里插入图片描述

  • 1、RUNNING
    (1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
    (02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  • 2、 SHUTDOWN
    (1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
    (2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

  • 3、STOP
    (1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
    (2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

  • 4、TIDYING
    (1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
    (2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
    当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

  • 5、 TERMINATED
    (1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
    (2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

4、线程池参数详解

  • corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true
  • maximumPoolSize:线程池允许的最大线程池数量
    keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
  • unit:超时时间的单位
  • workQueue:工作队列,保存未执行的Runnable 任务
  • threadFactory:创建线程的工厂类
  • handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。
4种拒绝策略:
一:AbortPolicy()中止策略。当触发拒绝策略时候,直接抛出拒绝执行的异常。
java.util.concurrent.RejectedExecutionException异常
二:DiscardPolicy() 丢弃策略。触发拒绝策略的时候,静悄悄的丢掉这个任务,不触发任何动作
三:DiscardOldestPolicy()丢弃老策略。如果线程池未关闭,就弹出队列头不的元素,然后尝试运行
四:CallerRunsPolicy()调用者运行策略。当触发拒绝策略时候,只要线程池没有关闭,就先处理最新调用的线程。

5、怎么计算线程池核心数的大小

一般说来,大家认为线程池的大小经验值应该这样设置:
	(其中N为CPU的个数)
如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1

计算公式:
  核心线程数 = CPU核数 /1-阻塞系数)     例如阻塞系数 0.8,CPU核数为4
       则核心线程数为20

注:阻塞系数公式

Blocking Coefficient(阻塞系数) = 阻塞时间/(阻塞时间+使用CPU的时间)

6、线程池有哪几种工作队列

  • 1、ArrayBlockingQueue
    是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • 2、LinkedBlockingQueue
    一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
  • 3、SynchronousQueue
    一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • 4、PriorityBlockingQueue
    一个具有优先级的无限阻塞队列。

newFixedThreadPool和newSingleThreadExecutor的工作队列是LinkedBlockingQueue
newCachedThreadPool的工作队列是SynchronousQueue
newScheduledThreadPool 的工作队列是DelayedWorkQueue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值