线程池总结

线程池的执行流程总结:

从创建线程池的参数分析:

1.提交新线程,线程池会在线程池中分配一个空闲线程,用于执行线程任务。

2.参数(int corePoolSize):核心线程数

  • 如果线程池中不存在空闲线程,则线程池会判断当前"存活的线程数"是否小于核心线程数
  • 如果小于核心线程数,线程池会创建一个新线程(核心线程)去处理新线程任务。

3.参数(BlockingQueue<Runnable> workQueue):工作队列

如果大于核心线程数,线程池会检查工作队列(workQueue),如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行。

4.参数(int maximumPoolSize):最大线程数

如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize.

  • 如果当前"存活线程数"没有达到最大线程数maximumPoolSize,则创建一个新线程(非核心线程)执行新线程任务。
  • 如果当前"存活线程数"已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务。

线程池的状态:

线程池的状态分为:RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED

RUNNING:运行状态,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0.该状态的线程池会接收新任务,并处理工作队列中的任务。

  • 调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态
  • 调用线程池的shutdownNow()方法,可以切换到STOP停止状态

SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;

  • 当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入 TIDYING状态。

STOP:停止状态,该状态下的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在进行中的任务。

当线程池中的任务为空时,进入TIDYING状态;

TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;

  • terminated()执行完毕,进入TERMINATED状态;

TERMINATED:终止状态,该状态表示线程池彻底关闭。

线程池的类型:

FixedThreadPool

线程数固定的线程池,使用Executors.newFixedThreadPool()创建

线程池参数:

  • 核心线程数和最大线程数一致
  • 非核心线程线程空闲存活时间,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue
java 复制代码
public class Test05 {
	public static void main(String[] args) {
		//创建一个固定大小的线程池
		ExecutorService executorService = Executors.newFixedThreadPool(4);
		
		for(int i=0;i<6;i++) {
			executorService.execute(new Task("线程"+i));
		}
		//关闭线程池
		executorService.shutdown();
	}
}
class Task implements Runnable{
	private String taskName;
	public Task(String taskName) {
		this.taskName=taskName;
	}
	@Override
	public void run() {
		System.out.println("启动线程==>"+this.taskName);
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("结束线程<="+this.taskName);
	}	
}

运行结果为:

启动线程==>线程0

启动线程==>线程3

启动线程==>线程2

启动线程==>线程1

结束线程<=线程2

结束线程<=线程1

结束线程<=线程0

结束线程<=线程3

启动线程==>线程5

启动线程==>线程4

结束线程<=线程4

结束线程<=线程5

执行分析:一次性放入6个线程,由于线程池只有固定的4个线程,因此,前4 个任务会同时执行,等到有空闲线程后,才会执行后面的两个任务。

工作机制:

a、提交线程任务

b、如果线程数少于核心线程数,创建核心线程执行任务

c、如果线程数等于核心线程数,把任务添加到LinkedBlockingQueue阻塞队列

d、如果线程执行完任务,去阻塞队列领取任务,继续执行

使用场景:适用于处理CPU密集型任务,确保CPU在长期被工作线程使用的情况下,尽可能少的分配线程,即适用执行长期的任务。

CachedThreadPool

可缓存线程池,线程数根据任务动态调整的线程池,使用Executors.newCacheThreadPool()创建

线程池参数:

  • 核心线程数为0
  • 最大核心线程数为Integer.MAX_VALUE
  • 工作队列是SynchronousQueue同步队列
  • 非核心线程空闲存活是时间为60秒

将线程池改为CachedThreadPool

java 复制代码
ExecutorService executorService = Executors.newCachedThreadPool();

执行结果:

启动线程==>线程1

启动线程==>线程3

启动线程==>线程2

启动线程==>线程0

启动线程==>线程4

启动线程==>线程5

结束线程<=线程0

结束线程<=线程3

结束线程<=线程5

结束线程<=线程2

结束线程<=线程1

结束线程<=线程4

执行分析:由于这个线程池的实现会根据任务数量动态调整线程池的大小,所以6个任务可一次性全部同时执行。

工作机制:

a、提交线程任务

b、因为核心线程数为0,所有任务直接添加到SynchronousQueue工作队列

c、判断是否有空闲线程,如果有,就去取出任务执行

d、如果没有空闲线程,就创建一个新线程

e、执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。

使用场景:用于并发执行大量短期的小任务。

SingleThreadExecutor

单线程化的线程池,使用Executors.newSingleThreadExecutor()创建

线程池参数:

  • 核心线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • 非核心线程数空闲存活时间为0秒

将线程池改为SingleThreadExecutor

java 复制代码
ExecutorService executorService = Executors.newSingleThreadExecutor();

执行结果:

启动线程==>线程0

结束线程<=线程0

启动线程==>线程1

结束线程<=线程1

启动线程==>线程2

结束线程<=线程2

启动线程==>线程3

结束线程<=线程3

启动线程==>线程4

结束线程<=线程4

启动线程==>线程5

结束线程<=线程5

执行结果分析: 一次只执行一个线程,当该线程彻底执行完毕,再执行下一个线程。

使用场景:适用于串行执行任务的场景,将任务按顺序执行。

ScheduleThreadPool

能实现定时、周期性任务的线程池,,使用Executors.newScheduledThreadPool()创建

线程池参数:

  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0

将线程池改为ScheduledThreadPool

java 复制代码
ExecutorService executorService = Executors.newScheduledThreadPool(3);

延迟3秒钟后,任务只执行1次

java 复制代码
executorService.schedule(new Task("线程A"),3,TimeUnit.SECONDS);

延迟2秒钟后,每隔3秒执行一次

java 复制代码
executorService.scheduleAtFixedRate(new Task("线程A"), 2, 3, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(new Task("线程A"), 2, 3, TimeUnit.SECONDS);

FixedRate和Fixedelay的区别:

  • FixedRate是指任务总是以固定时间间隔触发,不管任务执行多长时间;
  • Fixedelay是指,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次的任务;

使用场景:周期性执行任务,并且需要限制线程数量的需求场景。

线程池的配置参数总结

1.核心线程数:corePoolSize

int corePoolSize

核心线程数也就是线程池的最小线程数量。

  • 核心线程会一直存活,不会被回收,除非设置了核心线程超时时间;
  • 在创建线程池后,默认情况下,线程池中没有任何线程,调用excute()方法添加一个任务。

当线程池中没有空闲线程时,查看是否超过核心线程数:

  • 线程数量小于核心线程数,则马上创建新的核心线程来执行线程任务。
  • 线程数量大于核心线程数,则查看工作队列是否超出。

CPU密集型和IO密集型的区别:

(1)CPU密集型

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。

在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序

CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。

(2)IO密集型

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。

I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。

(3)先看下机器的CPU核数,然后在设定具体参数:

自己测一下自己机器的核数

System.out.printlnRuntime.getRuntime().availableProcessors());

即CPU核数 = Runtime.getRuntime().availableProcessors()

(4)分析下线程池处理的程序是CPU密集型还是IO密集型

CPU密集型:corePoolSize = CPU核数 + 1

IO密集型:corePoolSize = CPU核数 * 2

2.最大线程数:maximumPoolSize

int maximumPoolSize

是指线程池中允许的最大线程数量。

当工作队列已满,且存活线程数超过了核心线程数时,线程池判断**"** 存活线程数 " 是否超过最大线程数:

  • 未超过最大线程数:创建新线程来执行该任务。
  • 超过最大线程数:执行拒绝策略。
3.非核心的空闲线程的存活时间:keepAliveTime

long keepAliveTime

当线程数大于核心线程数时,空闲的线程等待新任务到达的最大时间,如果超过这个时间线程还没有需要执行的任务,该空闲线程就会销毁。

4.keepAliveTime的单位:unit

TimeUnit unit

keepAliveTime的单位,枚举类型的TimeUnit类。

5.阻塞工作队列:workQueue

BlockingQueue<Runnable> workQueue

在任务执行之前,用来存储任务的工作队列,此队列只保存由excute()方法提交的Runnable类型的任务。

当存活的线程数大于核心线程数,查看工作队列:

  • 工作队列未满:将新的请求任务加入工作队列;
  • 工作队列已满:线程池判断是否超过最大线程数。

5.1 ArrayBlockingQueue

基于数组有界阻塞队列,FIFO(先进先出)。

  • capacity:队列初始化大小
  • fair:表示该队列中的可重入锁是否公平,默认为false

当线程池中已经存在最大数量的线程时候,再请求新的任务,这时就会将任务加入工作队列的队尾,一旦有空闲线程,就会取出队头执行任务。

5.2 LinkedBlockingQueue

基于链表的误解阻塞队列,默认最大容量为Integer.MAX_VALUE,可认为是无限队列,FIFO(先进先出)。

指定工作队列大小,则最大线程数量的限制是有效的。

5.3 SynchronousQueue

可以将SynchronousQueue看作是一个没有容量的阻塞队列,它严格遵循FIFO(先进先出)的原则,但特殊的是,它不会保存任何元素,而是直接在不同的线程间进行传递。

6.线程工厂:threadFactory

ThreadFactory threadFactory

用于创建一个新线程时使用的工厂,可以用来设置线程名。

没有特别声明时,使用Executors工具类提供的默认线程工厂Executors.defaultThreadFactory()。

自定义线程工厂时,要实现ThreadFactory接口,重写newThread()方法。

7.拒绝策略:handler

RejectedExecutionHandler handler

当线程池内的线程被耗尽,并且工作队列已满,对于新提交的任务,将使用拒绝策略进行处理。

7.1 AbortPolicy:丢弃线程任务,并抛出异常

没有特别声明时,使用默认的拒绝策略defaultHandler

7.2 DiscardOldestPolicy:将工作队列的对头移除,线程池重新执行该线程任务

7.3 DiscardPolicy:直接丢弃该任务

7.4 CallerRunPolicy:线程池没有关闭时,线程自己调用run方法

7.5 自定义的拒绝策略:实现RejectedExecutionHandler接口

重写void rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法

相关推荐
曹申阳3 小时前
2. JVM的架构模型和生命周期
jvm·架构
琪露诺大湿4 小时前
JavaEE-多线程初阶(4)
java·开发语言·jvm·java-ee·基础·1024程序员节·原神
王佑辉7 小时前
【jvm】Full GC
jvm
九鼎科技-Leo7 小时前
C# 内存管理与对象生命周期在面向对象设计中的重要性
jvm·c#
王佑辉21 小时前
【jvm】堆空间分代思想
jvm
为啥不能修改昵称啊1 天前
静态数据区,堆,栈
java·jvm·算法
救苦救难韩天尊1 天前
《JVM第7课》堆区
jvm
飞升不如收破烂~2 天前
路由和网关的关系可以用一个生活中的比喻
java·服务器·网络·jvm·生活
韩子谦2 天前
栈虚拟机和寄存器虚拟机,有什么不同?
java·jvm
wainyz2 天前
JVM 类加载器
jvm