线程池总结

线程池的执行流程总结:

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

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)方法

相关推荐
Yz987630 分钟前
Hadoop里面MapReduce的序列化与Java序列化比较
java·大数据·jvm·hadoop·分布式·mapreduce·big data
pjx9872 小时前
JVM 性能调优与监控
jvm·测试工具
无奇不有 不置可否2 小时前
JVM基础篇学习笔记
java·jvm
pjx9872 小时前
JVM 案例研究与实战经验
jvm
pjx9876 小时前
JVM 语言与生态
jvm
猿究院---王某人7 小时前
Java 内存模型(JMM)
java·开发语言·jvm
爱棋笑谦11 小时前
JVM基础
jvm
懒洋洋大魔王20 小时前
7.Java高级编程 多线程
java·开发语言·jvm
只吹45°风20 小时前
JVM-类加载器的双亲委派模型详解
jvm·类加载器·双亲委派