什么是线程池?为什么要使用线程池?
线程池是一种多线程的处理方式,将任务提交给线程池中的线程去处理,任务的处理由线程池管理。如果在开发过程中给每个任务都去单独的创建一个独立的线程,那么系统的资源很快就会被耗尽。创建线程和销毁线程的系统开销较大,需要的时间开销可能比业务处理的时间还要长,所以要减少创建线程和销毁线程的次数,利用线程池中的线程来分别执行不同的任务。
线程池有什么作用?
- 提高效率,线程池中的核心线程会一直处于存活状态,当需要使用时,直接去线程池中取就可以,不需要创建线程,系统效率会得到提高。
- 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
ThreadPoolExecutor的构造方法
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;
}
corePoolSize | 指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去; |
maximumPoolSize | 指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量; |
keepAliveTime | 当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁; |
unit | keepAliveTime的单位 |
workQueue | 任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种; |
threadFactory | 线程工厂,用于创建线程,一般用默认即可; |
handler | 拒绝策略;当任务太多来不及处理时,如何拒绝任务; |
任务队列详解:
- SynchronousQueue
SynchronousQueue是一个没有容量的队列,它不会把任务缓存起来,提交的任务会立即执行。所以,提交的任务数大于最大线程数时,就会执行拒绝策略。
SynchronousQueue案例:
package com.wyx.learn.concurrent.ThreadPool;
import java.util.concurrent.*;
/**
* @program: concurrent
* @description: 直接提交队列
* @author: Wang Yongxin
* @create: 2019-11-19 10:47
**/
public class SynchronousQueueTest {
private static ExecutorService executorService;
public static void main(String[] args) {
executorService = new ThreadPoolExecutor(
1, //核心线程数
3, //最大线程数
2, //生存时间
TimeUnit.SECONDS, //生存时间单位
new SynchronousQueue<Runnable>(), //任务队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
);
for(int i = 0;i < 4;i++){
executorService.submit(new Thread(()->{
System.out.println(Thread.currentThread().getName());
}));
}
executorService.shutdown();
}
}
运行结果:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@27bc2616 rejected from java.util.concurrent.ThreadPoolExecutor@3941a79c[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 3]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.wyx.learn.concurrent.ThreadPool.SynchronousQueueTest.main(SynchronousQueueTest.java:17)
只能执行前三个任务,第四个任务提交时,已经超出了最大线程数,所以触发拒绝策略,抛出了异常。
2、ArrayBlockingQueue
ArrayBlockingQueue是有界任务队列,当线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
ArrayBlockingQueue案例:
package com.wyx.learn.concurrent.ThreadPool;
import java.util.concurrent.*;
/**
* @program: concurrent
* @description: 有界任务队列
* @author: Wang Yongxin
* @create: 2019-11-19 11:10
**/
public class ArrayBlockingQueueTest {
private static ExecutorService executorService;
public static void main(String[] args) {
executorService = new ThreadPoolExecutor(
2, //核心线程数
3, //最大线程数
2, //生存时间
TimeUnit.SECONDS, //生存时间单位
new ArrayBlockingQueue<>(2), //任务队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
);
for(int i = 0;i <6;i++){
executorService.submit(new Thread(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
executorService.shutdown();
}
}
运行结果:
当创建的任务数大于5时,会触发拒绝策略。
pool-1-thread-3
pool-1-thread-2
Exception in thread "main" pool-1-thread-1
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7530d0a rejected from java.util.concurrent.ThreadPoolExecutor@27bc2616[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.wyx.learn.concurrent.ThreadPool.ArrayBlockingQueueTest.main(ArrayBlockingQueueTest.java:24)
pool-1-thread-2
pool-1-thread-3
3、LinkedBlockingQueue
使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。
4、PriorityBlockingQueue
除了第一批任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
PriorityBlockingQueue案例:
package com.wyx.learn.concurrent.ThreadPool;
import java.util.concurrent.*;
/**
* @program: concurrent
* @description: 优先队列
* @author: Wang Yongxin
* @create: 2019-11-19 11:32
**/
public class PriorityBlockingQueueTest {
private static ExecutorService pool;
public static void main( String[] args )
{
//优先任务队列
pool = new ThreadPoolExecutor(
3,
4,
1000,
TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for(int i=0;i<20;i++) {
pool.execute(new ThreadTask(i));
}
}
}
class ThreadTask implements Runnable,Comparable<ThreadTask>{
private int priority;
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public ThreadTask() {
}
public ThreadTask(int priority) {
this.priority = priority;
}
//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
public int compareTo(ThreadTask o) {
return this.priority > o.priority ? -1 : 1 ;
}
public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列
Thread.sleep(1000);
System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
priority:0,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-3
priority:1,ThreadName:pool-1-thread-2
priority:18,ThreadName:pool-1-thread-3
priority:19,ThreadName:pool-1-thread-1
priority:17,ThreadName:pool-1-thread-2
priority:15,ThreadName:pool-1-thread-1
priority:16,ThreadName:pool-1-thread-3
priority:14,ThreadName:pool-1-thread-2
priority:12,ThreadName:pool-1-thread-1
priority:13,ThreadName:pool-1-thread-3
priority:11,ThreadName:pool-1-thread-2
priority:10,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-2
priority:9,ThreadName:pool-1-thread-3
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-2
priority:5,ThreadName:pool-1-thread-3
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-2
拒绝策略
为防止系统资源被耗尽,在创建线程池时,任务队列都会选择创建有界任务队列,当创建的线程数达到设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:
AbortPolicy | 该策略会直接抛出异常,阻止系统正常工作; |
CallerRunsPolicy | 把任务队列中的任务放在调用者线程当中运行; |
DiscardOledestPolicy | 该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交; |
DiscardPolicy | 该策略会默认丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失; |