线程池介绍
线程池的重要性
什么是池,软件中的池,可以理解为计划经济。如创建十个线程,创建十个线程在线程池之后,因为创建每个线程是要花蛮大开销的。用了线程池之后,可以复用线程池中的线程,达到提高资源复用率的作用。
如果不使用线程池,每个人物都新开一个线程处理。非常的耗费资源。反复的线程创建和销毁,会给服务器的开销和垃圾回收器的压力增大,这样我们就希望有固定数量的线程来执行任务。
问题一:反复创建线程开销大。
问题二:过多的线程会占用太多内存。
解决以上两个问题的思路
用少量的线程-避免内存占用过多。
让这部分线程都保持工作,且可以反复执行任务-避免生命周期的损耗。
线程池的好处:
加快响应速度
合理利用CPU和内存
统一管理资源和线程中的任务。
线程池适合应用的场合
服务器接受到大量请求时,使用线程池技术是非常适合的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。
实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。
增减线程的时机
创建和停止线程池
线程池构造函数的参数
corePoolSize核心线程数
线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务。核心线程数是线程池中常驻的线程数。
maxPoolSize最大线程数
线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量maxPoolSize.线程池中最大的线程数。
keepAliveTime保持存活时间
workQueue任务存储队列
threadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
Handler由于线程池无法接受你所提交的任务的拒绝策略。
添加线程规则
1.如果线程数小于corePoolSize,及时其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
2.如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列。
3.如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
4.如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务。
增减线程的特点
1.通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
2.线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
3.通过设置maximumPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务。
4.是只有在队列填满时才创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize。
线程池应该手工创建还是自动创建
手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。
自动创建线程池(也就是直接调用JDK封装好的构造函数)可能带来哪些问题。
newFixedThreadPool创建线程池
/**
* 描述: 演示newFixedThreadPool
*/
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-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-4
pool-1-thread-2
pool-1-thread-4
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1
pool-1-thread-4
newFixedThreadPool源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
线程池里的线程数量设定为多少比较合适?
正确的创建线程池的方法
1.根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程取什么名字等等。
2.线程池里的线程数量设定为多少比较合适?
CPU密集型(加密,计算hash等):最佳线程数为CPU核心数的1-2倍左右。
耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于cpu核数的很多倍。以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考Brain Geotz推荐的计算方法:
线程数=CPU核心数*(1+平均等待时间/平均工作时间)
停止线程池的正确方法
/**
* 描述: 演示关闭线程池
*/
public class ShutDown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
Thread.sleep(1500);
// List<Runnable> runnableList = executorService.shutdownNow();
executorService.shutdown();
executorService.execute(new ShutDownTask());
// boolean b = executorService.awaitTermination(7L, TimeUnit.SECONDS);
// System.out.println(b);
// System.out.println(executorService.isShutdown());
// executorService.shutdown();
// System.out.println(executorService.isShutdown());
// System.out.println(executorService.isTerminated());
// Thread.sleep(10000);
// System.out.println(executorService.isTerminated());
// executorService.execute(new ShutDownTask());
}
}
class ShutDownTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
}
}
}
shutDown
运行了这个方法之后,并不一定会停止线程池,执行了这个方法后,只是初始化整个关闭过程。会等待线程池中所有的任务执行完之后,才会停止线程池。执行了shutDown之后,再往线程池中提交任务会被拒绝。
isShutdown
判断线程池中的任务是否停止,即判断是否执行了shutDown的方法。
isTerminated
返回线程是否完全终止,正在执行的任务,队列中的任务都执行完了,才会返回true.
shutdownNow
立刻停止线程,用interrupted方法去打断线程。等待中的任务直接返回。返回的是一个列表。
keepAlive和线程工厂
keepAliveTime
如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,他们就会被终止。
ThreadFactory 用来创建线程
新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的的线程都在同一个线程组,拥有同样的NORM_PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。
工作队列
有3种最常见的队列类型
1.直接交接:SynchronousQueue
2.无界队列:LinkedBlockingQueue
3.有界队列:ArrayBlockingQueue
演示内存溢出的情况
newFixedThreadPool
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成大量的内存,可能会导致OOM。
/**
* 描述: 演示newFixedThreadPool出错的情况
*/
public class FixedThreadPoolOOM {
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executorService.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Connected to the target VM, address: '127.0.0.1:63800', transport: 'socket'
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at threadpool.FixedThreadPoolOOM.main(FixedThreadPoolOOM.java:14)
线程池用法演示
newFixedThreadPool
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成大量的内存,可能会导致OOM。
newSingleThreadExecutor
只有一个线程,
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看书,这里和刚才的newFixedThreadPool的原理基本一样,只不过把线程池直接设置成1,所以这也会导致同样的问题,也就是当请求堆积的时候,可能会占用大量的内存。
newCachedThreadPool
可缓存线程池
特点:无界线程池,具有自动回收多余线程的功能。默认回收多余的线程时候为60s。
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这里的弊端在于第二参数maximumPoolSize被设置为Integer.MAX_VALUE, 这可能会创建非常多的线程,甚至导致OOM.
用到的队列是synchronous queue,内部容量为0,不能把线程放在队列中。
任务进来之后,直接给到线程池,线程池大小为整形的最大值。
工作队列中有三中最常见的队列类型:
1.直接交接:SynchronousQueus
2.无界队列:LinkedBlockingQueue
3.有界的队列:ArrayBlockingQueue
newScheduledThreadPool
支持定时及周期性任务执行的线程池。
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
// 五秒后执行任务
// threadPool.schedule(new Task(), 5, TimeUnit.SECONDS);
// 以一定频率运行任务,最开始1秒后执行任务,以后每隔3秒执行任务
threadPool.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS);
}
}
对比线程池的特点
FixedThreadPool
固定数量的线程池
CachedThreadPool
可缓存线程池
特点:具有自动回收多余线程的功能
ScheduledThreadPool
支持定时及周期性任务执行的线程池
SingleThreadExecutor
单线程的线程池:它只会用唯一的工作线程来执行任务。
原理和FixedThreadPool是一样的,但是此时的线程数量被设置为了1.
FixedThreadPool和SingleThreadExecutor的Queue是LinkedBlockingQueue?
新来任务的数量我没办法去估计,所以就用一个无界队列去接受。
CachedThreadPool使用的Queue是SynchronousQueue?
不需要直接存储线程
ScheduledThreadPool来说,它使用的是延迟队列DelayedWorkQueue
WorkStealingPool是JDK1.8加入的,这个线程池和之前的有很大的不同,适合于任务会产生子任务的任务。有一定的窃取的能力。这个线程池用的比较少。
五虎上将收服线程池
钩子
拒绝时机
1.当Executor关闭时,提交新任务会被拒绝。
2.以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时。
4种拒绝策略
AbortPolicy 抛出异常拒绝
DiscardPolicy 默默的丢弃
DiscardOldestPolicy 丢弃最久的任务
CallerRunsPolicy 让调用的线程执行任务
钩子方法
每个任务执行前后,方便做日志打印和统计。
/**
* 描述: 演示每个任务执行前后放钩子函数
* 暂停线程
*/
public class PauseableThreadPool extends ThreadPoolExecutor {
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
private boolean isPaused;
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,
handler);
}
/**
* 钩子方法
* 每次执行任务之前,都会执行这个函数
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
try {
while (isPaused) {
// 休眠线程
unpaused.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 暂停函数
*/
private void pause() {
lock.lock();
try {
isPaused = true;
} finally {
lock.unlock();
}
}
/**
* 唤醒函数
*/
public void resume() {
lock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我被执行");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10000; i++) {
pauseableThreadPool.execute(runnable);
}
Thread.sleep(1500);
pauseableThreadPool.pause();
System.out.println("线程池被暂停了");
Thread.sleep(1500);
pauseableThreadPool.resume();
System.out.println("线程池被恢复了");
}
}
Executor家族的辨析
线程池组成部分
线程池管理器
工作线程
任务队列
任务接口(Task)
线程池、ThreadPoolExecutor、ExecutorService、Executor、Executors等这么多和线程池相关的类,大家都是什么关系?
Executors 是一个工具类
Executor是一个顶层接口,只有一个方法
ExecutorService实现了Executor
线程池实现任务复用的原理
相同的线程执行不同任务