这里写目录标题
线程池的自我介绍
如果不使用线程池,每个任务都新开一个线程处理,for 循环创建线程当任务数量上升到 1000
这样开销太大,我们希望有固定数量的线程,来执行这 1000 个线程,这样就避免了反复创建并销毁线程所带来的开销问题。
线程池的好处
- 加快响应速度、合理利用CPU和内存、统一管理
- 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
- 线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
线程池适合应用的场合
服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理
线程池构造参数
运行流程:
1、线程池创建,准备好 core
数量的核心线程,准备接受任务
2、新的任务进来,用 core
准备好的空闲线程执行。
(1) 、core
满了,就将再进来的任务放入阻塞队列
中。空闲的core
就会自己去阻塞队列获取任务执行
(2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max
指定的数量
(3) 、max
都执行好了。Max-core
数量空闲的线程会在 keepAliveTime
指定的时间后自动销毁。最终保持到 core
大小
(4) 、如果线程数开到了 max
的数量,还有新任务进来,就会使用reject
指定的拒绝策略
进行处理
3、所有的线程创建都是由指定的 factory
创建的。
面试:
一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在70 个被安排上了。剩下 30 个默认拒绝策略。
corePoolSize和maxPoolSize
corePoolSize 指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时再创建新线程去执行任务
线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量 maxPoolSize
如果线程数小于 corePoolSize ,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
如果线程数等于(或大于) corePoolSize 但少于 maximumPoolSize ,则将任务放入队列。
如果队列已满,并且线程数小于 maxPoolSize ,则创建一个新线程来运行任务。
如果队列已满,并且线程数大于或等于maxPoolSize ,则拒绝该任务。
例子:
线程池核心池大小为5,最大池大小为10,队列为100。因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100。当队列已满时,将创建最新的线程 maxPoolSize,最多到10个线程,如果再来任务就拒绝。
增减线程的特点
-
通过设置corePoolSize和maximumPoolSize相同就可以创建固定大小的线程池。
-
线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
-
通过设置maximumPoolSize为很高的值,例如Integer.MAX_ VALUE,可以允许线程池容纳任意数量的并发任务。
-
是只有在队列填满时才 创建多于 corePoolSize 的线程,所以如果你使用的是无界队列(例如 LinkedBlockingQueue),那么线程数就不会超 corePoolSize。
keepAliveTime
如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止
ThreadFactory
新的线程是由 ThreadFactory 创建的,默认使用 Executors.defaultThreadFactory() ,创建出来的线程都在同一个线程组拥有同样的NORM_ PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory ,那么就可以改变线程名、线程组、优先级、是否是守护线程等。
WorkQueue
有 3 种最常见的队列类型
- 直接交接:SynchronousQueue
- 无界队列:LinkedBlockingQueue
- 有界的队列:ArrayBlockingQueue
线程池应该手动创建还是自动创建
newFixedThreadPool
由于传进去的 LinkedBlockingQueue 是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。
public class FixedThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
执行结果:
pool-1-thread-1
pool-1-thread-4
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
Process finished with exit code -1
newSingleThreadExecutor
这里和上面的 newFixedThreadPool 的原理基本一样,只不过把线程数直接设置成了1,所以这也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。
public class SingleThreadExector {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
执行结果
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
Process finished with exit code -1
CachedThreadPool
可缓存线程池,无界线程池,具有自动回收多余线程的功能
这里的弊端在于第二个参数 maximumPoolSize 被设置为了 Integer.MAX_VALUE,这可能会创建数量非常多的线程甚至导致O0M。
public class CacheThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
执行结果
pool-1-thread-843
pool-1-thread-852
pool-1-thread-841
pool-1-thread-842
pool-1-thread-847
pool-1-thread-848
pool-1-thread-850
Process finished with exit code -1
ScheduledThreadPool
public class ScheduledThreadPoolTEst {
public static void main(String[] args) {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
threadPool.schedule(new Task(), 5, TimeUnit.SECONDS); // 延迟五秒之后执行
threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS); // 每隔三秒执行
}
}
正确的创建线程池的方法要根据不同的业务场景,自己设置线程池参数,分析阿里规约的图的第二二个例子,比如我们的内存有多大,我们能不能接受任务被拒绝等等
线程池里的线程数量设定为多少比较合适?
-
CPU密集型(加密、计算hash等):最佳线程数为 CPU 核心数的 1 - 2 倍左右。
-
耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于 cpu 核心数很多倍,以 JVM 线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考 Brain Goetz 推荐的计算方法,精准一点根据不同程序去做压测
线程数 = CPU 核心数 * (1 + 平均等待时间/平均工作时间)
以上4种线程池的构造函数的参数
FixedThreadPool 和 SingleThreadExecutor的Queue 的队列设置成 LinkedBlockingQueue
CachedThreadPool 使用的Queue是 SynchronousQueue
ScheduledThreadPool 使用的是延迟队列 DelayedWorkQueue
停止线程池
shutdown
:关闭线程池的方法之一,调用线程池的此方法后,不再接受新的任务,待所有任务都执行关闭后,进行关闭
isShutdown
:线程池是否已经关闭,当调用shutdown之后,此值为true
isTerminated
:线程池所有任务是否已经关闭,包括正在执行和队列中的任务都结束了
awaitTermination
:相对比较弱,等待一段时间,阻塞一段时间,要是线程执行完了,为true,否则为false,可以进行线程池检测
shutdownNow
:暴力关闭所有线程
拒绝策略
拒绝时机
当 Executor 关闭时,提交新任务会被拒绝。以及当 Executor 对最大线程和工作队列容量使用有限边界并且已经饱和时
AbortPolicy
:弃任务并抛出RejectedExecutionException异常DiscardPolicy
:丢弃任务,但是不抛出异常DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新提交被拒绝的任务CallerRunsPolicy
:由调用线程(提交任务的线程)处理该任务