面试扫盲:一文弄懂Java 线程池常见知识点

线程池

线程池优势

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

线程池的创建

Java 通过Executors 来提供四种创建线程池的方法:

  • Executors.newFixThreadPool(n) : 创建指定个数的线程池
  • Executors.newSingleThreadExecutor: 创建只有一个线程的线程池
  • Executors.newCacheThreadPool: 适用于不定个数的短期任务的执行,通过内部的调度,根据实际的情况动态地调整线程池中的线程数量

接下来,让我们看一下部分底层创建源码,可以发现,源码都是使用ThreadPoolExecutor 来创建的。

<code>    

public

static

ExecutorService

newFixedThreadPool

(

int

nThreads)

{

return

new

ThreadPoolExecutor(nThreads, nThreads,

0L

, TimeUnit.MILLISECONDS,

new

LinkedBlockingQueue()); }

public

static

ExecutorService

newCachedThreadPool

()

{

return

new

ThreadPoolExecutor(

0

, Integer.MAX_VALUE,

60L

, TimeUnit.SECONDS,

new

SynchronousQueue()); }

public

static

ExecutorService

newSingleThreadExecutor

()

{

return

new

FinalizableDelegatedExecutorService (

new

ThreadPoolExecutor(

1

,

1

,

0L

, TimeUnit.MILLISECONDS,

new

LinkedBlockingQueue())); } /<code>

其实,我们可以直接使用ThreadPollExecutor 来创建线程池,这也是阿里巴巴所推荐的,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

在《Java开发手册》中,阐述了直接使用Executors 的两大弊端:

  • FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  • CachedThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

使用ThreadPoolExecutor 创建线程池(阿里推荐)

首先来看一下ThreadPoolExecutor 的各项参数

<code>    

public

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue 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; } /<code>

七大参数

  1. corePoolSize: 线程池中常驻核心线程数
  2. maximumPoolSize: 线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  3. keepAliveTime:多余的空闲线程的存活时间,当前的线程池的数量超过corePoolSize 时,当空闲时间达到keepAliveTime 值时,多余的空闲线程会被销毁直到剩下corePoolSize 个线程为止。
  4. unit:keepAliveTime 的单位
  5. workQueue:阻塞任务队列,被提交但尚未被执行的任务(当任务队列满时,线程池会扩充线程数,扩充后的线程数会小于maximumPoolSize)
  6. threadFactory:表示生成线程的线程工厂,一般使用默认便可
  7. RejectedExecutionHandler:拒绝策略,当阻塞队列已满而且活跃线程数已经达到了maximumPoolSize 后,会启动拒绝策略,拒绝请求的加入

四大拒绝策略

  1. AbortPolicy(默认):该策略直接抛出RejectedExecutionException 异常阻止系统的正常运行
  2. CallerRunsPolicy:该策略不会抛弃任务也不会抛出异常,而是将任务退回到调用者
  3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中再次提交该任务
  4. DiscardPolicy:直接丢弃任务,不给予任何的处理,也不抛出异常。

使用ThreadPoolExecutor 创建线程池的例子

<code>        ExecutorService executorService = 

new

ThreadPoolExecutor(

2

,

5

,

5

L, TimeUnit.SECONDS,

new

LinkedBlockingQueue(

3

), Executors.defaultThreadFactory(),

new

ThreadPoolExecutor.AbortPolicy()); /<code>

线程池的工作流程

  1. 在创建了线程池后,等待提交过来的任务请求
  2. 当调用了execute() 方法添加了一个请求任务后,线程池会做如下判断:
  3. 如果正在运行的线程池数量小于corePoolSize,那么马上创建线程运行这个任务
  4. 如果正在运行的线程数量大于或等于corePoolSize时,那么会将请求任务放到阻塞队列中
  5. 如果阻塞队列已经满了,而且正在运行的线程数量小于maximumPoolSize,那么会创建非核心线程来立即运行这个任务
  6. 如果阻塞队列已经满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动拒绝策略
  7. 当一个线程执行完任务时,会从队列中取出下一个任务出来继续执行
  8. 当一个线程的空闲时间大于kiveAliveTime 时,会关闭一些线程,直到线程数达到corePoolSize 为止
面试扫盲:一文弄懂Java 线程池常见知识点

流程图

线程池线程数的合理配置

CPU 密集型

CPU 密集型需要大量的计算,没有阻塞,CPU 一直在全速运行

参考公式:CPU 核数 + 1个线程的线程池

IO 密集型

IO 密集型,需要大量的IO操作,即大量的阻塞 在单线程上运行IO密集型的任务会导致浪费大量CPU运算能力浪费在等待上,所以在IO密集型任务中运行多线程k可以大大加速程序的运行

参考公式:CPU 核数/(1-阻塞系数),阻塞系数在0.8-0.9 之间

Runnable and Callable

- Runnable 没有返回值 - Callable 有返回值

<code>

class

MyThread

implements

Runnable

{

public

void

run

()

{ } }

class

MyThread2

implements

Callable

<

Integer

>

{

public

Integer

call

()

{

return

123

; } }

public

class

Test

{

public

static

void

main

(String[] args)

throws

Exception

{ FutureTask futureTask =

new

FutureTask<>(

new

MyThread2()); Thread thread =

new

Thread(futureTask,

"aa"

); thread.start(); System.out.println(futureTask.get()); } }/<code>


专栏

Java面试通关100问

作者:Java识堂

19.9币

241人已购

查看


分享到:


相關文章: