Java线程池内容记录
一、 线程池
实现了对线程的复用,统一管理和维护线程,减少没有必要的开销。
为什么要用线程池?
为了提高效率,需要将一些业务采用多线程的方式去执行。几乎所有需要异步或并发执行任务的程序都可以使用线程池。
线程池的概念和连接池是类似的。在Java集合中存储大量的线程对象,每次执行异步操作或者多线程操作时,直接从集合中拿到线程对象执行方法,不需要每次创建和销毁线程。
二、 Jdk自带的线程池:
Jdk中基于Executors工具类提供了很多种线程池,一般不会用。
2.1 newFixedThreadPool
这个线程池的线程数的固定的,构建线程池的时候指定。线程是懒加载,当线程池创建好后,线程池中是没有线程的,是随着任务的提交将线程在线程池中创建出来。如果任务过来,没有空闲线程了,会把任务放在阻塞队列里(LinkedBlockingQueue无界队列)。直到有空闲线程,则从阻塞队列里获取一个任务进行处理。
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
threadPool.execute(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-thread1");
});
threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "-thread2");
});
}
如果是局部变量在方法中新创建的线程池,在使用完毕之后要执行shutdown()方法,避免线程无法结束。
如果是全局的线程池,很多业务都会到,使用完成后不要shutdown,因为其他业务也要使用这个线程池。
2.2 newSingleThreadExecutor
单例线程池,线程池中只有一个工作线程在处理任务,如果任务过来没有空闲线程,则进入阻塞队列中排队等待。当前
如果业务需要顺序处理,则可以采用newSingleThreadExecutor这种线程池。
2.3 newCachedThreadPool
是一个可缓存的线程池。当第一次提交任务时,线程池中没有线程,会直接新建一个工作线程,这个线程执行完后,如果60秒内没有任务可以执行,会销毁这个线程。如果在等待60秒期间有任务进来,则再次拿到这个线程使用。如果后续提交任务时,没有空闲线程时,则新创建线程去执行。
这个线程池的最大线程数是Integer.MAX_VALUE,代表了只有有任务过来,就会有线程去执行处理,如果线程数量太多,会消耗很多的资源。实际中应避免使用这种方式创建线程池。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2.4 newScheduleThreadPool
定时任务的线程池,这个线程池就是能以一定周期去执行一个任务,或者是延迟多久执行一个任务一次
是在ThreadPoolExecutor线程池的基础上实现了定时任务的功能,原理是基于DelayQueue实现的延迟执行。周期性执行是任务执行完毕后,再次扔回到阻塞队列。
public class Executors {
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
}
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
}
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
}
2.5 newWorkStealingPool
工作窃取线程池
这个线程池是基于ForkJoinPool构建的线程池。任务过来
在ThreadPoolExecutor中只有一个阻塞队列存放当前任务,一个任务只能由一个线程来完成。
ForkJoin可以将一个大任务拆分成多个小任务,放到线程的阻塞队列中。让其他的空闲线程去处理有任务的线程的阻塞队列中的任务。这种线程池中一个线程有一个阻塞队列。
核心思想:工作窃取/分而治之,希望没有工作线程处于空闲状态,让每个线程使用率更多。
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
- 怎么拆分呢?
- 使用场景:
三、自定义线程池的使用
在实际使用中,一般是基于ThreadPoolExecutor新建一个自定义线程池来用的,他提供了7个参数。可以手动设置线程池的核心属性,以配置的方式控制,也利于按照实际使用情况来修改。
自定义线程池,可以细粒度的控制线程池,管理内部的属性,设置Thread信息,帮助后期排查问题。
因为使用Jdk提供的几种创建线程的方式,可以自定义的参数少,最多有2个(核心工作线程数+threadFactory),对线程池的控制力度粗。
public ThreadPoolExecutor(
int corePoolSize, // 核心工作线程(当前任务执行结束后,不会被销毁)
int maximumPoolSize, // 最大工作线程
long keepAliveTime, // 非核心工作线程在阻塞队列位置等待的时间
TimeUnit unit, // 非核心工作线程在阻塞队列位置等待时间的单位
BlockingQueue<Runnable> workQueue, // 任务在没有核心工作线程处理时,任务先放到阻塞队列中
ThreadFactory threadFactory, // 构建线程的线程工厂,可以设置thread的一些信息
RejectedExecutionHandler handler) { // 当线程池无法处理投递过来的任务时,执行当前的拒绝策略
}
示例demo:
package com.example.mpdemo.thread;
import java.util.concurrent.*;
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int corePoolSize = 2;
int maximumPoolSize = 5;
long keepAliveTime = 10;
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("thread-test");
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
threadFactory,
new MyRejectedExecution());
// 处理没有返回结果的任务
threadPoolExecutor.execute(() -> {
System.out.println("thread-execute");
});
// 处理有返回结果的任务,也可以处理没有返回结果的任务
Future<Object> future = threadPoolExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("thread-submit");
return "success";
}
});
Object result = future.get();
threadPoolExecutor.shutdown();
}
private static class MyRejectedExecution implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝策略测试");
}
}
}
四、Jdk提供的拒绝策略:
- AbortPolicy:当前拒绝策略在无法处理任务时,直接抛出异常。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- CallerRunsPolicy:当前拒绝策略在无法处理任务时,将任务交给调用者来处理
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
- DiscardPolicy:当前策略在线程池无法处理任务时,将任务丢掉
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- DiscardOldestPolicy:当前策略在线程池无法处理任务时,将队列中最早的任务丢掉,将当前任务再次尝试交给线程池处理
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
- 自定义策略,按照业务需求自己处理实现拒绝策略
步骤:实现RejectedExecutionHandler接口,重写rejectedExecution
五、自定义线程池的核心参数设置?
一般有3个主要的参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- workQueue:工作队列
关与参数的理解简单描述:
有新任务时,如果线程池的工作线程数量<核心线程数,就会创建新的线程,直到达到核心线程数量。新任务过来时,核心线程数满了,则把任务先放到阻塞队列里,由线程池来调度。如果阻塞队列满了且正在运行的线程数量小于最大线程数,则创建非核心线程执行这个任务。如果达到可以创建的最大线程数,且阻塞队列已满,则执行拒绝策略。
我们任务有CPU密集型,和IO密集型,有代表的公式。一般建议是将核心线程池设置为CPU核心数 + 1,最大线程池大小设置为CPU核心数 x 2
实际上用的时候,自定义线程池时候,使用配置设置参数,然后压测得到一个合理的数值,配置在实际项目中。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战