java线程池的使用
线程池的使用
标签(空格分隔): 多线程
Executors工具类(创建池与提交任务)
通过工具类,可以创建线程池
常见的几种线程池创建
通过Executors的静态方法创建线程池
1.创建固定大小的线程池(可用于服务器瞬时削峰、限流,但需注意长时间持续高峰情况造成的队列阻塞)
ExecutorService executorService = Executors.newFixedThreadPool(int n);//n是线程池大小
2.创建可变大小的线程池(适用场景:快速处理大量耗时较短的任务)
ExecutorService executorService = Executors.newCachedThreadPool()
3.创建单线程的线程池(因为只有一个线程,所以提交的任务都是串行执行,可以解决并发执行带来的同步问题)
ExecutorService executorService = Executors.newSingleThreadExecutor()
ps:这个线程池,只会创建一次线程,用这个线程来执行所有提交过来的任务,并不是每来一次任务创建一次
4.创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行
ScheduledExecutorService executors.newScheduledThreadPool()
延迟3秒钟后执行任务
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("运行时间: " + sdf.format(new Date()));
}
}, 3, TimeUnit.SECONDS);
延迟1秒钟后每隔3秒执行一次任务
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("运行时间: " + sdf.format(new Date()));
}
}, 1, 3, TimeUnit.SECONDS);
5.创建一个具有抢占式操作的线程池(JDK1.8新增,适合使用在很耗时的操作)
newWorkStealingPool适合使用在很耗时的操作
ExecutorService executorService = Executors.newWorkStealingPool();
WorkStealingPool
工作窃取线程池
假设共有三个线程同时执行, A, B, C
当A,B线程池尚未处理任务结束,而C已经处理完毕,则C线程会从A或者B中窃取任务执行,这就叫工作窃取
假如A线程中的队列里面分配了5个任务,而B线程的队列中分配了1个任务,当B线程执行完任务后,它会主动的去A线程中窃取其他的任务进行执行WorkStealingPool 背后是使用 ForkJoinPool实现的
public class T11_WorkStealingPool {
public static void main(String[] args) throws IOException {
// CPU 核数
System.out.println(Runtime.getRuntime().availableProcessors());
// workStealingPool 会自动启动cpu核数个线程去执行任务
ExecutorService service = Executors.newWorkStealingPool();
service.execute(new R(10000)); // 我的cpu核数为12 启动13个线程,其中第一个是1s执行完毕,其余都是2s执行完毕,
// 有一个任务会进行等待,当第一个执行完毕后,会再次偷取第十三个任务执行
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
service.execute(new R(2000));
}
// 因为work stealing 是deamon线程,即后台线程,精灵线程,守护线程
// 所以当main方法结束时, 此方法虽然还在后台运行,但是无输出
// 可以通过对主线程阻塞解决
System.in.read();
}
static class R implements Runnable {
int time;
R(int time) {
this.time = time;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + time);
}
}
}
通过ExecutorService实例提交任务
a.通过sumbit提交任务,带Future返回值,任务类型可以为Runable和Callable型
1. 当任务类型为Runnable型的时候
Future future = executorService.submit(runnable)
执行执行完毕后,返回null,future.get(),将阻塞主线程,获取null值
2. 当任务类型为Callable型的时候
Future future = executorService.submit(callable)
任务执行完毕后,返回执行结果,执行结果类型与Callable泛型一致,future.get()阻塞主线程,并获取返回值
b.通过execute提交任务,没有返回值,任务类型只能是Runable类型
executorService. (runnable)
ThreadPoolExecutor创建方式
ThreadPoolExecutor提供了四个构造方法,提供不同的参数类型
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小
3 keepAliveTime long 线程最大空闲时间
4 unit TimeUnit 时间单位
5 workQueue BlockingQueue<Runnable> 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略。
线程池的关闭
在大多数人开发过程中,大家关注的是如何创建、使用线程池,但很少有人去关系线程池的关闭,甚至忘记调用shutdown()方法,这可能导致内存溢出。
注意:FixedThreadPool的核心线程不会自动超时关闭,必须要手动的调用shutdown关闭线程池,不然会导致内存溢出。
线程池自动关闭的两个条件:
1.线程池的引用不可达
2.线程池中没有线程;
两个关闭方法差异
1.shutdown()
线程池虽然关闭,但是队列中已有的任务仍然会继续执行
2.shutdownNow()
强制关闭线程池,线程的任务队列里的线程将不会被执行,而正在被执行的线程也会中断(可以在interrupted中处理)
正确的关闭线程池,需要考虑以下3个问题
1.如何拒绝新来的请求任务
2.如何处理线程池等待队列里的任务
3.如何处理正在执行的任务
线程池的拒绝策略
默认的拒绝策略
ThreadPoolExecutor源码中可以看到以下内容
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常
1.AbortPolicy(默认,丢弃并抛异常)
ThreadPoolExecutor.AbortPolicy
丢弃任务并抛出RejectedExecutionException异常。
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
2.DiscardPolicy(静默丢弃,不会给出任何异常提示)
ThreadPoolExecutor.DiscardPolicy
建议是一些无关紧要的业务采用此策略
3.DiscardOldestPolicy(喜新厌旧的拒绝策略)
ThreadPoolExecutor.DiscardOldestPolicy
它会丢掉队列最前面的任务,然后接收新来的任务,典型的是一种喜新厌旧的策略方式
4.CallerRunsPolicy(我干不了,你自己干吧)
ThreadPoolExecutor.CallerRunsPolicy由调用线程处理该任务
你提交给我,但是我没时间干呀,你自己干吧。