【Beautiful JUC Part.2】线程池 治理线程的最大法宝
一、线程池的重要性
1、什么是池
如果不使用线程池,每个任务都新开一个线程处理
- 一个线程,直接创建
- 当10个线程时候,for循环创建线程
- 当任务数量上升到1000
- 这样开销太大,我们希望有固定的数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。
2、为什么要使用线程池?
问题一:反复创建线程开销大
问题二:过多的线程会查勇太多的内存
解决以上两个问题的思路:
- 用少量的线程——避免内存占用过多
- 让这部分线程都保持工作,且可以反复执行任务——避免声明周期的损耗
3、线程池的好处
加快响应速度
合理利用CPU和内存
统一管理
4、线程池适合应用的场合
-
服务器接受到大量请求的时候,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。(Tomcat)
-
实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。
二、创建和停止线程池
1、线程池构造函数的参数
-
corePoolSize指的是核心线程数
- 线程池在完成初始化后,默认情况下,线程池中没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
-
maxPoolSize指的是最大线程数
- 线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程有一个上限,这就是最大量maxPoolSize
-
keepAliveTime
- 如果线程池当前的线程数多余corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止。
-
ThreadFactory用来创建线程
- 新的线程是由ThreadFactory来创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程,拥有同一的NORM_PRIORITY优先级并且都不是守护线程。
- 如果自己指定ThreadFactory,那么就可以改变线程名,线程组,优先级、是否是守护线程等。
-
有三种最常见的队列类型:
- 直接交接:SynchronousQueue,这种队列没有缓冲作用,需要把maxPoolSize设置的大一些。
- 无界队列:LinkedBlockingQueue,可以解决很大量的任务问题,线程可以无限扩展,有风险,可能会造成OOM异常。
- 有界的队列:ArrayBlockingQueue,可以设置队列的大小。
2、线程添加规则
- 如果线程数小于corePollSize,即是其他工作线程处于空闲状态,也会创建一个新线程来运行新任务。
- 如果线程数等于(或者大于)corePoolSize但少于maximumPoolSize,则将任务放到队列中。
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
- 如果队列已满,并且线程数大于或者等于maxPoolSize,则拒绝该任务(使用Handler)。
特点:
-
通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
-
线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它。
-
通过设置maximumPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务。
-
是只有在队列填满的时候才创建多于corePoolSize的线程,所以如果你使用的时无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize。
3、线程池应该手动创建还是自动创建
手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽的风险。
①JDK自带:newFixedThreadPool
代码
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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());
}
}
执行效果
都是在1,2,3,4线程执行。看一下这个FixedThreadPool源码
由于传进去的LinkedBlockingQUeue是没有容量上限的,所以当请求数量越来越多的时候,并且无法即是处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。
示例代码
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 描述:演示出错的情况
*/
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();
}
}
}
将添加VM内存大小吸纳之
②JDK自带:newSigleThreadExecutor
测试代码
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
③JDK自带:newCachedThreadPool
可缓存线程池,无界线程池,具有自动回收多余线程的功能。
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
也可能会导致OOM
④JDK自带:newScheduledThreadPool
支持定时及周期性任务执行的线程池
package threadpool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
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);
}
}
使用的是延迟队列
4、正确创建线程池的方法
根据不同的业务场景,自己设置线程池的参数,比如我们的内存有多大,我们想给线程取什么名字等等。
线程池里的线程数量设定为多少比较合适?
5、停止线程池的正确方法
①shutDown方法
测试代码
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ShutDown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executorService.execute(new ShutDownTask());
}
Thread.sleep(1500);
//System.out.println(executorService.isShutdown());
executorService.shutdown();
//System.out.println(executorService.isShutdown());
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) {
e.printStackTrace();
}
}
}
运行结果
在运行1.5s之后爆出异常,但是我们之前加入到了线程池当中的任务仍在继续执行。
- 可以通过isShutdown()方法判断是否开始被终止,isTerminated()方法可以返回是否真的被结束了(所有的线程任务都执行完了)。
- boolean b = executorService.awaitTermination(3L, TimeUtin.SECONDS);可以在3秒钟返回时候被结束。执行期间是阻塞的。
- executorService.shutdownNow(),立刻关闭线程池,返回没有执行的任务列表。
三、任务太多、怎么拒绝?
提交新任务的时候,怎么拒绝?
1、拒绝时机
- 当Executor关闭时,提交新任务会被拒绝
- 以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时
2、四种拒绝策略
-
AbortPolicy
- 直接抛出一个异常,没有提交成功
-
DiscardPolicy
- 默默地把新任务丢弃
-
DiscardOldestPolicy
- 丢弃最老的任务,腾出空间存放刚刚提交的新任务
-
CallerRunsPolicy
- 如果线程池没办法处理了,哪个线程提交的任务,哪个线程帮助线程池去运行任务
四、钩子方法,给线程池加点料
- 每个任务执行前后
- 日志、统计
- 代码演示
1、初始版本
package threadpool;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述:演示每个任务执行的前后都可以放钩子函数
*/
public class PauseableThreadPool extends ThreadPoolExecutor {
private boolean isPaused;
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
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) {
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);
}
}
}
2、线程的恢复与暂停
package threadpool;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述:演示每个任务执行的前后都可以放钩子函数
*/
public class PauseableThreadPool extends ThreadPoolExecutor {
private boolean isPaused;
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
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("线程池被恢复了");
}
}
五、实现原理、源码分析
1、线程池组成部分
- 线程池管理器
- 工作线程
- 任务队列
- 任务接口(Task)
2、Executor家族?
线程池、ThreadPoolExecutor、ExecutorService、Executor、Executors等这么多相关的类,大家都是什么关系?
-
Executor接口最高级的接口
-
ExecutorService继承了这个接口,并加以扩展
-
AbstractExecutorService抽象类实现了ExecutorService接口
-
Executors是一个工具类,可以帮助我们获得一个线程池
上述提到的newFixedThreadPool这些,是通过ThreadPoolExecutor这个类实现的
-
ThreadPoolExecutor继承了AbstractExecutorService抽象类
-
在这基础之上有一些其他的方法
-
比如execute方法
六、线程池状态
- 避免任务堆积
- 避免线程过度增加
- 排查线程泄露
- 线程已经执行完毕却不能被回收