一、为什么需要线程池:
多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。
为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。
二、线程池的简介:
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程数超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的主要特点为:线程复用、控制最大并发数、管理线程。
三、使用线程池有哪些优势:
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。 -
附加功能:提供定时执行、定期执行、单线程、并发数控制等功能。
四、线程池架构:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
五、线程池的使用:
Executors 是一个工厂类,主要用来创建 ExecutorService: Java在JDK1.5之后,Java就包装了一个实现线程池的一个工厂类Executors,该工厂类有多个静态的方法newFixedThreadPool等。
Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService。
1.创建固定数量线程的线程池:
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
package com.fan.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
public static void main(String[] args) {
//一池5线程,使用线程的工具类Executors创建线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
try {
//10个顾客请求
for (int i = 1; i <= 10; i++) {
//执行 Runable接口
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName()+
"办理业务");
});
}//end-for
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool1.shutdown();//关闭
}
}
}
2.创建只有一个线程的 线程池: Executors.newSingleThreadExecutor()方法 newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
package com.fan.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
public static void main(String[] args) {
//一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
try {
//10个顾客请求
for (int i = 1; i <= 10; i++) {
//执行 Runable接口
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName()+
"办理业务");
});
}//end-for
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool2.shutdown();//关闭
}
}
}
3.创建一池可扩容的线程:
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
package com.fan.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
public static void main(String[] args) {
//一池5线程
//ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
//一池一线程
//ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
//一池可扩容的线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
try {
//20个顾客请求
for (int i = 1; i <= 20; i++) {
//执行 Runable接口
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName()+
"办理业务");
});
}//end-for
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool3.shutdown();//关闭
}
}
}
4.newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
线程池都有哪些状态:
RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。 SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。 STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。 TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。 TERMINATED(终结):terminated()方法结束后,线程池的状态就会变成这个。
线程池之ThreadPoolExecutor详解:
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险.
Executors 各个方法的弊端:
-
newFixedThreadPool 和 newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。 -
newCachedThreadPool 和 newScheduledThreadPool: 主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
ThreadPoolExecutor() 是最原始的线程池创建,也是阿里巴巴 Java 开发手册中明确规范的创建线程池的方式。 ThreadPoolExecutor线程池的7大参数:
创建一个线程池时的参数:
-
corePoolSize 表示线程池中的常驻核心线程数 当提交一个任务到线程池的时候,线程池将会创建一个线程来执行任务。这个值的大小设置非常关键,设置过小将频繁地创建或销毁线程,设置过大则会造成资源浪费。 线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。 -
maximumPoolSize 表示线程池能够容纳的同时执行的最大线程数 如果队列(workQueue))满了,并且已创建的线程数小于 maximumPoolSize,则线程池会进行创建新的线程执行任务。 如果等待执行的线程数 大于 maximumPoolSize,缓存在队列中; 如果 maximumPoolSize 等于 corePoolSize,即是固定大小线程池。 -
keepAliveTime 表示线程活动的保持时间 当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用; 当一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉。 -
TimeUnit :keepAliveTime 的单位 它的单位有:TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS -
workQueue 表示线程池中存放被提交但尚未被执行的任务的队列,即用于存放线程任务的阻塞队列。 维护着等待执行的 Runnable对象。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。 -
threadFactory 用于设置创建线程的工厂 它用来给每个创建出来的线程设置一个名字,就可以知道线程任务是由哪个线程工厂产生的。 -
handler 表示拒绝处理策略 表示当线程队列满了并且工作线程大于等于线程池的最大线程数(maxnumPoolSize)时如何来拒绝。 线程数量大于最大线程数,当超过workQueue的任务缓存区上限的时候,就可以调用该策略,这是一种简单的限流保护。
四种拒绝策略:
Discard:丢弃的意思 AbortPolicy:终止、中断策略 CallerRunsPolicy:调用者运行策略
线程池实现原理:
线程池的工作流程
tidying:整理,收拾的意思,这里是收尾整理状态 TERMINATED(终结)
线程池状态变化:
|