线程池
线程池构造方法的参数
参数名 | 类型 | 含义 |
---|---|---|
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
keepAliveTine | long | 保持存活时间 |
workQueuee | BlockingQueue | 任务存储队列 |
threadFactory | ThreadFactory | 当线程池需要新的线程的时候,会使用threadFactory来生成新的线程 |
Handler | RejectedExecutionHandler | 由于线程池无法接受你所提交的任务的拒绝策略 |
添加线程规则
- 如果线程数小于corePoolSize,创建一个新线程来运行新任务
- 如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则讲任务放入队列
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程
- 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝
例子:线程池大小为5,最大池大小为10,队列为100。
因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100。当队列已满时,将创建新的线程maxPoolSize,最多到10个线程,如果再来任务,就拒绝。
增减线程的特点
- 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
- 通过设置maximumPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务。
- 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。
ThreadFactory 用来创建线程
- 默认使用Executors.defaultThreadFactory()
- 创建出来的线程都在同一个线程组
- 如果自己指定的ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等。
工作队列
- 有三种最常见的队列类型:
- 直接交接:SynchronousQueue
- 无界队列:LinkedBlockingQueue
- 有界的队列:ArrayBlockingQueue
线程池创建(应该手动还是自动)
手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险
如果是自动创建线程池(即直接调用JDK封装好的构造方法)可能会带来一些问题。
调用JDK封装好的构造方法:
- newFixedThreadPool
先来看newFixedThreadPool,先给出一些代码例子
public class FixedThreadPoolTest {
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
运行结果
从运行结果就可以看出执行1000次任务始终都是1234,这是因为线程是被规定好的,我们来看看源码。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
第一个参数nThreads原本是corePoolSize,传进来4就变成4,第二个参数是nThreads代表的是maxPoolSize,但和corePoolSize一样,永远不会超过4,第三个参数keepAliveTime实际上是没有意义的,因为不会有线程会回收,第四个参数TimeUnit.MILLISECONDS是时间的一个单位,这里是毫秒,第五个参数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());
}
}
static class SubThread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(100000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- SingleThreadExecutor 单线程的线程池:只会唯一的工作线程来执行任务
public class SingleThreadExecutor {
public static void main(String[] args) {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new FixedThreadPoolTest.Task());
}
}
}
为什么会这样呢,我们来看看源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
核心数量和最大线程数量都是1,也传入了一个无限容量的队列,可以看出和刚才的newFixedThreadPool原理一样。
- CachedThreadPool 可缓存线程池:具有自动回收多余线程的功能。(默认时间60s)
public class CachedThreadPool {
public static void main(String[] args) {
final ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new FixedThreadPoolTest.Task());
}
}
}
有1000个任务创建1000个线程池,过一会没有任务可执行了就会回收线程。但这种线程池有弊端,具体看源码。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到核心线程数量为0,而弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM
- ScheduledThreadPool 支持定时及周期性任务执行的线程池
因此手动创建线程池更好。
线程池里的线程数量设定为多少比较合适
- CPU密集型(加密,计算hash等):最佳线程数为CPU核心数的1-2倍左右。
- 耗时IO型(读写数据库,文件,网络读写等):最佳线程数一般会大于CPU和学术很多倍
参考Brain Goets推荐的计算方法:
线程数=CPU核心数*(1+平均等待时间/平均工作时间)
workStealingPool(JDK1.8)
- 这个线程池和之前的有很大不同
- 子任务
- 窃取
停止线程池的相关方法
- shutdown 执行后停止
- isShutdown 判断开始执行停止
- isTerminated 判断整个程序是否停止
- awaitTermination 检测一段时间内是否执行完毕
- shutdownNow 立刻关闭线程池
4种拒绝策略
- AbortPolicy 直接抛出异常
- DiscardPolicy 丢弃处理
- DiscardOldestPolicy 丢弃最老,存在时间最久的
- CallerRunsPolicy 谁提交的任务谁去执行
钩子方法 (暂停和恢复线程池)
利用钩子函数可以做到暂停和恢复线程池
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 {
final PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
final 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("线程池被恢复了");
}
}