文章目录
- 线程池介绍
- 线程池的优点
- 线程池的执行流程
- 线程池池参数:
- 线程池的状态
- 常见的线程池
- FixedThreadPool(有限线程数的线程池):
- ScheduledThreadPool(定时线程池)
- scheduleWithFixedDelay
- SingleThreadExecutor(单一线程池)
- SingleThreadScheduledExecutor(单一定时线程池)
线程池介绍
线程池是一种线程管理的机制,它可以在应用程序中重复使用固定数量的线程,以减少线程创建和销毁的开销,提高资源利用率,并且可以控制并发线程数量,防止资源耗尽和系统崩溃。
在 Java 中,线程池通常使用 java.util.concurrent 包中的 ThreadPoolExecutor 类来实现。通过 ThreadPoolExecutor,你可以创建一个线程池,并指定线程池的大小、任务队列、拒绝策略等。
线程池的优点
“在线程池中执行任务”比“为每个线程分配一个任务”优势更多。通过重用现有的线程而不是创建线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。通过适当的调整线程池的大小,可以创建足够的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。
线程池的执行流程
- 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
- 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。
3.如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务;
4.如果大于核心线程数corePoolSize,线程池会检查工作队列;
5.如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;
6.如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;
如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建7.一个新线程(非核心线程)执行新线程任务;
8.如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策略处理新线程任务;
综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略。
线程池池参数:
线程池的参数包括以下几个方面:
1.核心线程数(Core Pool Size):线程池中保持活动的最小线程数。即使线程是空闲的,核心线程也不会被回收。当有新的任务提交时,线程池会创建新的线程,直到达到核心线程数为止。
2.最大线程数(Maximum Pool Size):线程池中允许存在的最大线程数。当有新的任务提交时,如果线程池中的线程数量小于最大线程数,则会创建新的线程来处理任务。如果线程数量已经达到最大线程数,后续的任务会被放入任务队列中等待执行。
3.任务队列(Work Queue):用于存放等待执行的任务的队列。当线程池中的线程数量达到核心线程数,并且任务队列已满时,后续的任务会根据拒绝策略来处理。
4.线程存活时间(Keep Alive Time):当线程池中的线程数量大于核心线程数时,空闲线程的最长存活时间。如果线程在空闲时间超过了这个值,就会被终止并从线程池中移除。
5.拒绝策略(Rejected Execution Handler):当线程池中的线程数量已经达到最大线程数,并且任务队列已满时,新提交的任务会根据拒绝策略来处理。常见的拒绝策略包括抛出异常、丢弃任务、阻塞调用者和丢弃队列中最旧的任务。
6.线程工厂
线程池的状态
RUNNING:
运行状态,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。
调用线程池的shutdown()方法,可以切换到SHUTDOWN关闭状态;
调用线程池的shutdownNow()方法,可以切换到STOP停止状态;
SHUTDOWN:
关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入TIDYING状态;
STOP:
停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;
线程池中执行的任务为空,进入TIDYING状态;
TIDYING:
整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;terminated()执行完毕,进入TERMINATED状态;
TERMINATED:
终止状态,该状态表示线程池彻底关闭。
常见的线程池
FixedThreadPool(有限线程数的线程池):
FixedThreadPool线程池的特点是他的核心线程数和最大线程数是一样的,你可以把它看作成是一个固定线程数的线程池,因为他不会去将超出线程数的线程缓存到队列中,如果超出线程数了,就会按线程拒绝策略来执行。可以看下面的示例代码
public class ThreadPoolMain {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 20; i++) {
executor.execute(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行了");
});
}
executor.shutdown();
}
}
ScheduledThreadPool(定时线程池)
这个线程就是为了定时而发明的,它支持定时或周期性执行任务,比如10秒钟执行一次任务。
实现的方法具体如下
public class ThreadPoolMain {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
Runnable task02 = new Runnable() {
@Override
public void run() {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":scheduleAtFixedRate 开始执行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":scheduleAtFixedRate 执行完成了");
}
};
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":开始执行");
service.scheduleAtFixedRate(task02, 10, 10, TimeUnit.SECONDS);
}
}
这里我们可以看到,scheduleAtFixedRate设置了两个参数,一个是initialDelay,另外一个是period,initialDelay表示项目启动时,需要隔多久时间才开始执行第一次,period则表示后面继续执行的间隔时间。这里你会发现,scheduleAtFixedRate会严格执行定时时间来执行,他不会管之前正在执行的定时方法有没有执行完成,他还是会照样不变10秒执行一次。
scheduleWithFixedDelay
public class ThreadPoolMain {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
Runnable task03 = new Runnable() {
@Override
public void run() {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":scheduleWithFixedDelay 开始执行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":scheduleWithFixedDelay 执行完成了");
}
};
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(time+":开始执行");
service.scheduleWithFixedDelay(task03, 10, 10, TimeUnit.SECONDS);
}
}
scheduleWithFixedDelay方法其实和scheduleAtFixedRate方法很相像,但是他们有个区别,就是等不等的区别,像scheduleAtFixedRate方法,他不会等到之前的线程是否有没有执行完成,他照样还是会按照约定好的定时时间去执行任务,而scheduleWithFixedDelay不同,他会等待线程执行完成之后,上一次线程结束时间来开始计算。简单来说他们的区别就是scheduleAtFixedRate的执行周期是按照线程任务的开始执行时间,scheduleWithFixedDelay的执行周期是按照线程任务的结束时间。
SingleThreadExecutor(单一线程池)
这个线程很适用于需要按照提交顺序去执行线程的场景,因为在他的线程池中,只有一个线程可以执行,他的原理其实跟FixedThreadPool有点相像,只不过他的核心线程数和最大线程数都是1,这样当提交者去提交线程的时候,就必须先让线程池中的线程执行完成之后才会去执行接下来的线程。这样就保证了线程的顺序性,而这种顺序性,前面几种线程的机制是做不到的。
SingleThreadScheduledExecutor(单一定时线程池)
这个线程池像是SingleThreadExecutor和ScheduledThreadPool生的娃,他有SingleThreadExecutor单一线程池的特性,一次只能执行一个线程,又可以完成ScheduledThreadPool线程池的定时功能。如果你能理解这个线程服务的能力,你就能理解这个线程的能力。