目录
方法二:newsSingleThreadExecutor()
4. DiscardOldestPolicy(丢弃最老策略)
什么是线程池
我们通过创建一个线程对象,并且实现Runnable接口就可以实现一个简单的线程。 但很多时候,我们不止会执行一个任务。如果每次都是如此的创建线程 -> 执行任务->销毁线程,会造成很大的性能开销。
那能否一个线程创建后,执行完一个任务后,又去执行另一个任务,而不是销毁。这就是线程池。 这也就是池化技术的思想,通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接 获取,避免多次重复创建、销毁带来的开销。
为什么要用线程池(线程池的优势)
-
降低资源消耗:线程复用,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行,并且控制最大并发数量。
-
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系 统的稳定性,使用线程池可以进行统一分配,调优和监控。
创建线程池(三大方法)
三个基本方法均是通过 Executor 框架实现的。
方法一:newFixedThreadPool(int)
这个方法创建了一个拥有固定数量线程的线程池。这里的int参数指定了池中线程的数量。如果任何一个线程因为执行任务时抛出异常而结束,那么线程池会补充一个新的线程来替换它。
ExecutorService executor = Executors.newFixedThreadPool(10);
案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 池子大小为5
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
这段代码创建了一个包含5个线程的固定大小线程池,并模拟了10个线程请求服务。由于线程池中只有5个线程,所以任何时候最多只有5个线程可以同时被服务。其他的线程将会排队等待,直到有线程变得可用。当所有任务执行完毕后,代码会关闭线程池以释放资源。
特点
-
核心线程数等于最大线程数。
-
工作队列是一个无界队列(
LinkedBlockingQueue
),这意味着如果任务提交速度超过处理速度,队列会无限增长,可能会导致内存溢出。
方法二:newsSingleThreadExecutor()
这个方法创建了一个只有一个线程的线程池,即单线程执行器。所有提交的任务都会按照顺序依次执行。
ExecutorService executor = Executors.newSingleThreadExecutor();
案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 有且只有一个固定的线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
这段代码创建了一个单线程的线程池,并模拟了10个线程请求服务。由于线程池中只有一个线程,所以任务会按照它们提交的顺序一个接一个地执行。当所有任务执行完毕后,代码会关闭线程池以释放资源。
特点
-
只有一个核心线程,没有非核心线程。
-
工作队列是一个无界队列(
LinkedBlockingQueue
)。 -
适用于需要保证任务顺序执行的场景。
方法三:newCachedThreadPool()
这个方法创建了一个可根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。如果线程空闲超过60秒,则会被回收。
ExecutorService executor = Executors.newCachedThreadPool();
案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 一池N线程,可扩容伸缩
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 10; i++) {
// 模拟延时看效果
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 用完记得关闭
}
}
}
这段代码创建了一个可伸缩的线程池,并模拟了10个线程请求服务。由于线程池会根据需要创建新线程,所以理论上可以处理任意数量的任务。
特点
-
核心线程数为0,最大线程数为
Integer.MAX_VALUE
。 -
工作队列是一个无界队列(
SynchronousQueue
),这意味着如果当前没有可用线程,则会创建一个新线程,如果创建新线程失败,则任务会等待。 -
适用于短生命周期的异步任务,或者负载较轻的服务器。
注意:实际开发中不允许使用 Executor 去创建线程池,因为无论通过哪个方法创建都有OOM风险,而是通过 ThreadPoolExecutor 自定义创建线程池。
自定义线程池(七大参数)
Executor 提供的方法都是调用的 ThreadPoolExecutor,在实际开发中 Executor 提供的方法都有OOM的可能,所以我们一般直接使用 ThreadPoolExecutor 创建线程池,这就涉及到ThreadPoolExecutor 的七个重要参数。
ThreadPoolExecutor 源码如下:
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutor extends AbstractExecutorService {
private final int corePoolSize;
private final int maximumPoolSize;
private final long keepAliveTime;
private final TimeUnit unit;
private final BlockingQueue<Runnable> workQueue;
private final ThreadFactory threadFactory;
private final RejectedExecutionHandler handler;
private final Object acc;
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
// ... 其他方法 ...
}
1. corePoolSize(核心线程数)
线程池中始终保持的线程数量。当有新任务来时,线程池会优先使用核心线程来执行任务,如果核心线程忙,则任务进入工作队列等待。
2. maximumPoolSize(最大线程数)
线程池中允许的最大线程数量。当工作队列满了之后,如果还有新任务到来,线程池会尝试创建新的线程来处理任务,直到达到最大线程数。
3. keepAliveTime(非核心线程空闲存活时间)
当线程池中正在运行的线程数量超过corePoolSize
时,多余的空闲线程能够保持多久会被终止。用于控制线程池中的非核心线程的生命周期,以减少资源消耗。
4. unit(存活时间单位)
keepAliveTime
参数的时间单位。指定keepAliveTime
的时间单位,如秒、毫秒等。
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5. workQueue(工作队列)
用于存放待执行任务的阻塞队列。当所有核心线程都在忙碌时,新任务会被放入工作队列中等待执行。
6. threadFactory(线程工厂)
用于创建新线程的工厂。通过线程工厂可以自定义新线程的创建过程,例如设置线程的名称、优先级、是否为守护线程等。
7. handler(拒绝策略)
当任务太多,无法被线程池及时处理时,采取的策略。常见拒绝策略有:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
拒绝策略详解(四大拒绝策略)
1. AbortPolicy(中止策略)
直接抛出RejectedExecutionException
异常,阻止系统正常运行。适用于必须对任务提交失败做出响应的场景,比如记录日志或者向调用者反馈错误信息。
2. CallerRunsPolicy(调用者运行策略)
将任务回退给调用线程(提交任务的线程)来执行,即由提交任务的线程自己来运行这个任务。适用于提交任务的线程本身是空闲的,或者任务执行时间非常短,不希望任务被丢弃,也不希望创建新线程的场景。
3. DiscardPolicy(丢弃策略)
默地丢弃无法处理的任务,不抛出异常,也不记录日志。适用于任务丢失不会对系统造成影响的场景,或者在任务提交速度非常快,而线程池处理速度跟不上的情况下。
4. DiscardOldestPolicy(丢弃最老策略)
丢弃工作队列(阻塞队列)中最旧的任务(即等待时间最长的任务),然后尝试为新任务创建一个新线程(如果线程池尚未达到最大线程数)。适用于希望尽可能处理新任务,而不是旧任务的场景,这可以避免新任务长时间等待。
线程池底层工作原理
一张图概括线程池的底层执行流程:
1. 核心线程处理
如果核心线程数没有达到corePoolSize
,线程池会创建一个新的线程来执行这个任务,即使当前有空闲的核心线程。
2. 工作队列处理
如果核心线程数已经达到corePoolSize
,任务会被放入工作队列(workQueue
)中等待执行。此时,如果工作队列未满,任务将在队列中等待空闲的核心线程来执行。
3. 最大线程数检查
如果工作队列已满,线程池会检查当前的线程数是否已经达到maximumPoolSize
所指定的最大值。
4. 创建非核心线程
如果当前线程数没有达到maximumPoolSize
,线程池会创建一个新的非核心线程来执行任务。
5. 触发拒绝策略
如果当前线程数已经达到maximumPoolSize
且工作队列已满,线程池将触发拒绝策略。拒绝策略定义了如何处理无法被线程池接受的任务,例如通过抛出异常、调用者运行、丢弃任务或者丢弃队列中最旧的任务等。
如何设置线程池的线程数
任务可以分为 CPU 密集型和 I/O 密集型。
CPU密集型任务
CPU密集型任务线程数=CPU核心数+1
CPU密集型任务,比如单纯的数学计算任务,它不会涉及I/O操作,也就是说它可以充分利用CPU资源(如果涉及 I/O,在进行 I/O 的时候CPU是空闲的),不会因为I/O操作被阻塞,因此不需要很多线程,线程多了上下文开销反而会变多。
I/O 密集型任务
I/O 密集型任务线程数 = CPU核心数 * 2
I/O 密集型任务,有很多 I/O 操作,例如文件的读取、数据库的读取等等,任务在读取这些数据的时候,是无法利用CPU的,对应的线程会被阻塞等待 I/O 读取完成,因此如果任务比较多,就需要有更多的线程来执行任务,来提高等待 I/O 时候的CPU利用率。
标签:Java,java,队列,创建,并发,任务,线程,import From: https://blog.csdn.net/hrh1234h/article/details/144753733