记录学习过程,仅作学习笔记
前面已经学了线程与锁机制
接下来学习对线程的管理 - 线程池,重点难点
Java3y公众号的教程
Java并发编程札记-(六)JUC线程池-02ThreadPoolExecutor实现原理
大佬的详解:Java并发编程札记-(六)JUC线程池-01概述
线程池是线程的集合,在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用,可以大大的提升性能
平常使用线程都是直接创建,然后销毁
CreatThread1 thread1=new CreatThread1();
Thread myThread1=new Thread(thread1);
myThread1.start();
为每个请求创建线程有一定的缺点:
线程生命周期的开销非常高。每个线程都有自己的生命周期,创建和销毁线程所花费的时间和资源可能比处理客户端的任务花费的时间和资源更多,并且还会有某些空闲线程也会占用资源。
程序的稳定性和健壮性会下降,每个请求开一个线程。如果受到了恶意攻击或者请求过多(内存不足),程序很容易就奔溃掉了。
线程池可以通过重复利用已创建的线程降低线程创建和销毁造成的消耗
Executor是线程池顶级的接口,是线程池的基础
Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)
execute(Runnable)这么一个方法,用于执行已提交的Runnable任务
而在Executor下有多个子类
ExecutorService继承了Executor接口,用于提交一个用于执行的Runnable任务、试图停止所有正在执行的活动任务,暂停处理正在等待的任务、执行给定的任务
ExecutorService提供了线程池生命周期管理的方法
抽象类,继承ExecutorService接口,是ExecutorService的实现类
ScheduledExecutorService继承了ExecutorService,可安排在给定的延迟后运行或定期执行命令
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,实现了ScheduledExecutorService
提供一个可扩展的线程池实现,常用的线程池
需要详细了解
JDK1.7中新增的一个线程池,与ThreadPoolExecutor一样,同样继承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的两大核心类之一。与其它类型的ExecutorService相比,其主要的不同在于采用了工作窃取算法(work-stealing):所有池中线程会尝试找到并执行已被提交到池中的或由其他线程创建的任务。这样很少有线程会处于空闲状态,非常高效。这使得能够有效地处理以下情景:大多数由任务产生大量子任务的情况;从外部客户端大量提交小任务到池中的情况
CompeleteFuture、并发流等都是基于ForkJoinPool实现
ForkJoinPool
前面创建线程时使用的是Runnable接口,它是无返回值的,还有有返回值的创建线程的方法,就是继承Callable接口
Callable接口与Runnable接口类似
Future是一个未来对象,保存线程结果
执行Callable方式,需要Future的实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类
package com.company.Thread.Callable;
import java.util.concurrent.*;
public class MyCallable implements Callable<Integer> {
private int count=3;
public MyCallable(int count){
this.count=count;
}
@Override
public Integer call() throws Exception {
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程固定的线程池
ExecutorService pool= Executors.newFixedThreadPool(2);
//future接收返回值
Future future=pool.submit(new MyCallable(100));
Future future1=pool.submit(new MyCallable(200));
System.out.println(future.get());
System.out.println(future1.get());
//线程池关闭
pool.shutdown();
}
}
Java中常用的线程池有三个,最出名的当然是ThreadPoolExecutor,除此之外还有ScheduledThreadPoolExecutor、ForkJoinPool
推荐使用Executor工厂方法配置,有3种常见的使用场景的配置
Executors.newCachedThreadPool()(自动线填海无界线程池)
Executors.newFixedThreadPool(int)(固定大小的线程池)
Executors.newSingleThreadExecutor()(单个后台线程)
当然也可以手动配置
手动配置需要了解属性:
这个大佬的详解更清晰(Java3y公众号:线程池)
通过这些自定义属性,可以解析3个常用线程池配置,它们都在Executors类下
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
调用 execute 将重用以前构造的线程(如果线程可用)。
如果现有线程没有可用的,则创建一个新线程并添加到池中。
终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
因此,长时间保持空闲的线程池不会使用任何资源
一个固定线程数的线程池,它将返回一个corePoolSize和maximumPoolSize相等的线程池
使用单个worker线程的Executor
SynchronousQueue :
它将任务直接传输给工作队列workers,而不保持任务。如果不存在空闲线程,则会新建一个线程来执行任务
LinkedBlockingQueue:
无界队列,使用此队列会导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过最大值(maximumPoolSize无意义)
可以选择自定义ThreadPoolExecutor,它有4个构造方法
大致就是设置前面学到的各个属性
最复杂的一个:
线程数量corePoolSize、maximumPoolSize要点:
池中线程空闲时的活动时间maximumPoolSize:
排队策略要点:
当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝
拒绝任务策略:
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED
ctl对象一共32位,高3位保存线程池状态信息,后29位保存线程池容量信息。线程池的初始化状态是RUNNING
package com.company.Thread.Callable;
import java.util.concurrent.*;
public class MyCallable implements Callable<Integer> {
private int count=3;
public MyCallable(int count){
this.count=count;
}
@Override
public Integer call() throws Exception {
return count;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程固定的线程池
ExecutorService pool= Executors.newFixedThreadPool(2);
//future
Future future=pool.submit(new MyCallable(100));
Future future1=pool.submit(new MyCallable(200));
System.out.println(future.get());
System.out.println(future1.get());
//线程池关闭
pool.shutdown();
}
}
这个程序中启动线程池submit方法
/**
* 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
* 该 Future 的 get 方法在 成功 完成时将会返回 null。
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
submit又是调用execute方法
case1:如果线程池中运行的线程数量
case3:如果以上两种case不成立,即没能将任务成功放入阻塞队列中,且addWoker新建线程失败,则该任务由当前 RejectedExecutionHandler 处理
这就是Executor工厂方法配置线程池
shutdown()方法和shutdownNow()方法
shutdown() 按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务
shutdownNow()尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表
shutdown和shutdownNow详解
shutdown()和shutdownNow()的区别