【并发编程笔记】 ---- 线程池基础概念知识点

目录

1. 线程池介绍
2. 线程池参数
3. 添加线程规则
4. 线程池创建问题
5. 线程池关闭问题
6. 线程池拒绝问题
7. 线程池状态
8. 使用线程池注意点

1. 线程池介绍

线程池组成成分:

  • 线程池管理器:创建、停止线程等
  • 工作线程: 执行任务的线程
  • 任务队列: BlockingQueue
  • 任务接口(Task):任务

Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor
- Executor

1.1 为什么要使用线程池

  • 问题一: 反复创建线程开销大

  • 问题二: 过多的线程占用太多内存

  • 解决以上两个问题的思路

    • 用少量的线程,避免内存占用过多
    • 让这部分线程都保持工作,且可以反复执行任务-避免生命周期的损耗

1.2 线程池的好处

  • 加快响应速度
  • 合理利用CPU和内存
  • 统一管理

1.3 线程池适合应用的场合

  • 服务器接收到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

2. 线程池参数

2.1 线程池构造函数的参数

参数名类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTimelong保持存活时间
workQueueBlockingQueue任务存储队列
threadFactoryThreadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
HandlerRejectedExecutionHandler由于线程池无法接受你所提交的任务的拒绝策略

2.2 参数介绍

  • corePoolSize
    指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
  • maxPoolSize
    线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量maxPoolSize
  • keepAliveTime
    如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止
  • ThreadFactory
    用来创建线程
    新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等
  • workQueue
    工作队列
    有3种最常见的队列类型: 1) 直接交接:SynchronousQueue;2)无界队列: LinkedBlockingQueue; 3) 有界的队列: ArrayBlockingQueue

3. 添加线程规则

  • 如果线程数小于corePoolSize,即使其它工作线程处于空闲状态,也会创建一个新线程来运行新任务
  • 如果线程等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列
  • 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
  • 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务

在这里插入图片描述
是否需要增加线程的判断顺序是:

  • corePoolSize
  • workQueue
  • maxPoolSize

3.1 增减线程的特点

  1. 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
  2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
  3. 通过设置maximumPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务(队列有界限,线程无界限)
  4. 是只有在队列填满时才创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize(队列无界限)

3.2 例子

线程池:核心池大小为5,最大池大小为10,队列为100
因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100.当队列已满时,将创建最新的线程maxPoolSize,最多到10个线程,如果再来任务,就拒绝。

4. 线程池创建问题

4.1 线程池应该手动创建还是自动创建?

  • 手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险
  • 正确创建线程池的方法(根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程池取什么名字等)

4.2 线程池用法及演示

  • newFixedThreadPool
    由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM
  • newSingleThreadExecutor
    和newFixedThreadPool原理基本一样,只不过把线程数直接设置成了1,所以也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存
  • newCachedThreadPool
    可缓存线程池
    特点:无界线程池,具有自动回收多余线程的功能
    弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM
  • ScheduledThreadPool
    支持定时及周期性任务执行的线程池

newFixedThreadPool内存溢出情况

/**
 * 描述: 演示newFixedThreadPool出错的情况
 */
public class FixedThreadPoolOOM {
	private static ExecutorService executorService = Executors.newFixedThreadPool(1);

	public static void main(String[] args) {
		for (int i = 0; i < Integer.MAX_VALUE; i++) {
			executorService.execute(new SubThread());
		}
	}

}

class SubThread implements Runnable {

	@Override
	public void run() {
		try {
			Thread.sleep(1000000000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
在这里插入图片描述
newCacheThreadPool用法

public class CacheThreadPool {
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newCachedThreadPool();
		for (int i = 0; i < 1000; i++) {
			executorService.execute(new Task());
		}
	}
}

在这里插入图片描述
newScheduledThreadPool用法

public class ScheduleThreadPool {
	public static void main(String[] args) {
		ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
//		threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
		// 周期性打印,一开始延迟1秒打印,后面每隔3秒打印一次
		threadPool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
	}
}

在这里插入图片描述

4.3 线程池里的线程数量设定

  • CPU密集型(加密、计算hash等) : 最佳线程数为CPU核心数的1-2倍左右
  • 耗时IO型(读写数据库、文件、网络读写等) : 最佳线程数一般会大于cpu核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上
  • 线程数 = CPU核心数 * (1 + 平均等待时间 / 平均工作时间)

4.4 对比线程池的特点

ParameterFixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThreaded
corePoolSizeconstructor-arg0constructor-arg1
maxPoolSizesame as corePooSizeInteger.MAX_VALUEInteger.MAX_VALUE1
keepAliveTime0 seconds60 seconds00 seconds

阻塞队列分析

  • FixedThreadPool和SingleThreadExcutor的Queue是LinkedBlockingQueue
  • CachedThreadPool使用的Queue是SynchronousQueue
  • ScheduledThreadPool来说,它使用的是延迟队列DelayedWorkQueue

5. 线程池关闭问题

  • shutdown
    线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。
  • shutdownNow
    线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
  • awaitTermination
    线程池调用awaitTermination方法后,当前线程会被阻塞,直到线程池状态变为TERMINATED才返回,或者等待时间超时才返回,用来测试一段时间内线程池状态,主要作用是

6. 线程池拒绝问题

拒绝时机

  1. 当Executor关闭时,提交新任务会被拒绝
  2. 以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和

4种拒绝策略

  • AbortPolicy:抛出异常
  • DiscardPolicy: 默默丢弃,不抛出异常
  • DiscardOldestPolicy: 调用poll丢弃一个任务,执行当前任务
  • CallerRunsPolicy:使用调用者所在线程来运行任务

7. 线程池状态

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,但处理排队任务
  • STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务
  • TIDYING: 所有任务都已停止,workerCount为零时,线程会转换到TIDYING状态,并将运行terminate()钩子方法
  • TERMINATED: terminate()运行完成

8. 使用线程池注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄露
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值