文章目录
线程池 thread pool
百度概念:
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
java概念:
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。
使用线程池的原因:
就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力。
public static void main(String[] args){
//创建任务
Runnable task1 = new Task();
Runnable task2 = new Task();
Runnable task3 = new Task();
//创建只有一个线程的线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//提交任务
threadPool.execute(task1);
threadPool.execute(task2);
threadPool.execute(task3);
//关闭线程池
threadPool.shutdown();
输出的线程名称一样说明:一个线程执行了仨个任务,线程得到复用
优势:
- 降低资源消耗:通过重复利用已创建的线程池降低线程创建和销毁造成的消耗
- 控制线程并发数量,降低服务器压力,统一管理所有线程
- 提升系统响应的速度,节约时间。假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间
线程池应用场景:
- 网购商品秒杀
- 云盘文件上传和下载
- 12306网上购票系统
只要有并发的地方、任务数量大或小、每个任务执行时间长或短都可以使用线程池;合理设置线程池大小
ThreadPoolExecutor类
java.uitl.concurrent.ThreadPoolExecutor是线程池中最核心的一个类
public ThreadPoolExecutor(
- int corePoolSize:核心线程数量,当我们有一个任务提交到线程池时,如果当前运行的线程数量没有达到核心线程数量,就会新创建一个线程执行我们的任务
- int maximumPoolSize:最大线程数
- long keepAliveTime:最大空闲时间,空闲一定时间后,回收线程
- TimeUnit unit:时间单位(枚举类型)TimeUnit.SECONDS表示秒
- BlockingQueue< Runnable > workQueue:任务队列,相当于集合,两个条件:没有达到最大线程数,并且任务队列满了,创建新的线程。临时缓冲区
- ThreadFactory threadrFactoenry:线程工厂,它允许我们自己参与创建线程
- RejectedExecutionHandler handler:饱和处理机制,当我们线程池饱和,还有其他任务时进行饱和处理机制
)
四个构造方法:
7个参数:
-
**corePoolSize:**按照百分之80的情况设计核心线程数,剩下的百分之20利用最大线程数
- 核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。
-
maximumPoolSize:
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
-
keepAliveTime(线程活动保持时间):
线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率
-
TimeUnit(线程活动保持时间的单位):
-
可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
-
unit:
参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
-
-
workQueue:一个阻塞队列,用来存储等待执行的任务
-
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
-
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE
-
synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
-
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
-
-
threadFactory:
线程工厂,主要用来创建线程
public class Executors{ /** *默认的线程工厂 */ public static ThreadFactory defaultThreadFactory(){ return new DefaultThreadFactory(); } }
-
**handler:**表示当拒绝处理任务时的策略,有以下四种取值
线程池已满;线程无法扩容;没有空闲线程;任务队列已满,无法存储新任
- ThreadPoolExecutor.AbortPolicy:默认的拒绝策略,丢弃任务并抛RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:使用调用者线程直接执行被拒绝的任务
线程执行的方法:
-
execute():是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现。通过这个方法可以向线程池提交一个任务,交由线程池去执行。
-
submit():方法是在ExecutorService中声明的方法,AbstractExecutorService进行了具体的实现,和execute()方法不同,它能够返回任务执行的结果。
execute是Executor接口中的任务,作用是向线程池中提交Runnable任务(无返回值的任务),就是说execute适合无返回值得任务
Callable是有返回值的任务,任务执行结果封装到Future对象中,返回给调用者,调用者通过Future对象获取结果
submit()的三种方法:
方法一:
public static void main(String[] args) {
Task task1 = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程池
//submit提交任务
Future<?> future = threadPool.submit(task1);
//获取get()方法获取任务执行结果
try {
Object result = future.get();
System.out.println(result); //无返回值结果为null
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭线程池
threadPool.shutdown();
}
}
方法二:已经知道返回类型,只适用于执行结果的同时附带一个参数
public static void main(String[] args) {
Task task1 = new Task();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程池
//第二个参数可以传对象new Object(),12222数字,""字符串
Future<String> future = threadPool.submit(task1, "任务完成");
//获取get()方法获取任务执行结果
try {
String result = future.get();
System.out.println(result); //无返回值结果为null
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭线程池
threadPool.shutdown();
}
}
方法三:
public class ResultTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 5+10;
}
}
测试类:
public class RunableTest {
public static void main(String[] args) {
ResultTask tt = new ResultTask();
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程池
//提交任务
Future<Integer> future = threadPool.submit(tt);
//获取get()方法获取任务执行结果
try {
Integer result = future.get();
System.out.println(result); //无返回值结果为null
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭线程池
threadPool.shutdown();
}
}
}
execute与submit区别:
execute | submit | |
---|---|---|
位置(接口) | Executor | ExecutorService |
提交任务的类别 | Runnable任务 | Runnable和Callable任务 |
返回值类型 | void(无) | Future |
线程停止的方法:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务;
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务(List)。
原生方法创建线程池(自定义工厂)
-
自定义线程工厂
/** * 自定义线程工厂 * @author Lenovo * */ public class CustomThreadFactory implements ThreadFactory { /** * 定义计数器:防止出现线程安全问题 */ private final AtomicInteger i = new AtomicInteger(1); //初始值为1 @Override public Thread newThread(Runnable r) { //创建线程,并指定任务 Thread thread = new Thread(r); //自定义线程名称 //没创建一个线程,计数器递增一次 thread.setName("线程"+i.getAndIncrement()+"号"); return thread; } }
-
编写任务,实现接口
/** * 编写任务,实现Runnable接口 * @author Lenovo * */ public class Task implements Runnable{ @Override public void run() { //获得当前线程名称 System.out.println(Thread.currentThread().getName()); } }
-
测试类
public static void main(String[] args) { //创建三个任务 Task task1 = new Task(); Task task2 = new Task(); Task task3 = new Task(); /** * 创建线程池: * * 1.核心线程数为10 * 2.最大线程数为25 * 3.空闲线程存活时间为10 * 4.时间单位为秒 * 5.任务队列采用链式阻塞队列 * 6.线程工厂为自定义的线程工厂 * 7.任务拒绝策略为默认任务拒绝策略 * */ ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,25,10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new CustomThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); //提交任务 threadPool.execute(task1); threadPool.execute(task2); threadPool.execute(task3); //关闭线程池 threadPool.shutdown(); } }
输出结果: 线程1号
线程3号
线程2号说明自定义的线程池起作用了,创建成功。
扩展OOM:
OOM:
这通常是因为某时刻应用程序大量请求内存导致系统内存不足造成的,这通常会触发 Linux 内核里的 Out of Memory (OOM) killer,OOM killer 会杀掉某个进程以腾出内存留给系统用,不致于让系统挂掉。
【强制】线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1 ) FixedThreadPool和SingleThreadPool :
允许的请求队列长度为Integer.MAX_VALUE(2的31次方),可能会堆积大量的请求,从而导致OOM。
2 ) CachedThreadPool :
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
其他三种(不推荐)
其内部都是采用ThreadPoolExecutpr来创建线程池。
Executors类中提供的几个静态方法来创建线程池:
1.Executors.newFixedThreadPool(int)
创建固定容量大小的缓冲池
适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutpr(nThreads,nThreads
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory)
}
特点:核心线程数和最大线程数一样,意味着里面全是核心线程;
空闲线程存活时间为0毫秒,意味着空闲线程不会被销毁
LinkedBlockingQueue< Runnable>()有资源耗尽的风险(OOM)
/**
* 不带线程工厂的固定大小的线程池
* @author Lenovo
*
*/
ExecutorService threadPool = Executors.newFixedThreadPool(10);
2.Executors.newSingleThreadExecutor()
创建容量为1的缓冲池
适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
public static ExecutorService newSingleThreadExecutor() { //ThreadFactory threadFactory
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
//threadFactory 带工厂
));
}
特点:核心线程数和最大线程数一样,一个,都是核心线程;
空闲线程存活时间为0毫秒,意味着空闲线程不会被销毁
LinkedBlockingQueue< Runnable>()有资源耗尽的风险(OOM)
ExecutorService threadPool = Executors.newSingleThreadExecutor();
3.Executors.newCachedThreadPool()
创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
public static ExecutorService newCachedThreadPool() { //ThreadFactory threadFactory
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>() //同步队列
//threadFactory 带工厂
);
}
特点:0,都是非核心线程数,
允许的创建线程数量为Integer.MAX_VALUE,创建大量线程导致OOM
ExecutorService threadPool = Executors.newCachedThreadPool();
四个任务拒绝策略:
都是ThreadPoolExecutor的内部类,实现RejectedExcutionHandler接口
/**
* 拒绝执行处理器
*
*/
public interface RejectedExecutionHandler {
/**
* 拒绝执行任务
* r 被拒绝的任务
* executor 线程池
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
示例DiscardPolicy():
背景:
线程池中的数量为1,提交一个任务时就会被占满;任务队列大小也是1,在提交一个任务时也会被占满。
在提交任务工作中没有可使用线程,任务队列也满了。就会被拒绝
public class Test {
public static void main(String[] args) {
/**
* 创建线程池
*/
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1), //队列中只能放一个任务
new ThreadPoolExecutor.DiscardPolicy()); //直接丢弃被拒绝的任务
//提交任务
threadPool.execute(new Task(1));
threadPool.execute(new Task(2));
threadPool.execute(new Task(3));
//关闭线程池
threadPool.shutdown();
}
}
示例CallerRunsPolicy():
![](https://img-blog.csdnimg.cn/3c5453f3fc0e497c8fb7378e29044c7b.png)
当线程池没关闭时,直接调用任务中的run()方法,此时就是用调用者的线程执行的。性能下降
”3“是mian线程输出的, 所以它被调用者所在的线程执行了,也就是我们的main线程。(因为它从main线程来的,现在又回到了main线程。所以我们说它从哪里来回哪里去)
示例AbortPolicy():
源码的方法体中只有一段抛异常的代码
示例DiscardOldestPolicy():
源代码:
poll()方法的作用就是移除队列头部的元素,即移除队列头部任务。
实例背景:
队列任务两个,当第三个(任务)来的时候,任务2作为首任务移除
public class Test02 {
public static void main(String[] args) {
/**
* 创建线程池
*/
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
1,1,0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2), //任务队列2·
new ThreadPoolExecutor.DiscardOldestPolicy());
//提交任务,任务四个
threadPool.execute(new Task(1));
threadPool.execute(new Task(2));
threadPool.execute(new Task(3));
threadPool.execute(new Task(4));
//关闭线程池
threadPool.shutdown();
}
}
//输出:
//pool-1-thread-1:1
//pool-1-thread-1:3
//pool-1-thread-1:4
自定义拒绝策略:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(4,8,0,TimeUnit.SECONDS,new ArrayBlockingQueue(4),//表示阻塞队列的长度为4
//自定义:
new RejectedExecutionHandler(){
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
System.out.println(r.hashCode()+"is discarding..."); //自定义的拒绝方式
}
}//CallerRunsPolicy是ThreadPoolExecutor的内部类
);
参数设计分析:
核心线程池corePoolSize:按照80%的情况设计核心线程数,剩下的20%可以利用最大线程数处理。
任务队列长度workingQueue:
任务队列长度一般设计为:核心线程数 / 单个任务执行时间*2
比如:核心线程数为10,,单个线程执行任务时间为0.1秒,则队列长度可以设计为200。
最大线程数maxinumPoolSize:
要考虑核心线程数,还要参照系统每秒产生的最大任务数决定。
比如:系统每秒最大产生的任务1000个,那么,最大线程数=(最大任务数-任务队列长度)* 单个任务执行时间。即:maxinumPoolSize = (1000-200)* 0.1=80个。
最大空闲时间:
此参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,凭经验和系统产生任务的时间间隔合理设置一个值即可。