一、线程池的好处
1、 降低资源消耗
2、提高响应速度
3、 方便管理
线程复用、控制最大线程数、管理线程
二、三大方法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Executors工具类、3大方法
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//1. 创建单个线程的线程池
// ExecutorService threadPool = Executors.newFixedThreadPool(5); //2. 创建一个固定大小的线程池
// ExecutorService threadPool = Executors.newCachedThreadPool(); //3. 创建一个可伸缩的线程池
try {
// 使用了线程池之后,使用线程池来创建线程
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+" ok"); });
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
使用Executors创建线程池的弊端如下:
- FixedThreadPool和SingleThreadPool:允许的请求队列的长度为Integer.MAX_VALUE,可能会堆积大量的请求从而导致OOM
- CachedThreadPool和ScheduledThreadPool:允许创建的线程池数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
三、七大参数
1.Executors创建的线程池源码
//newSingleThreadExecutor()源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
)
);
}
//newFixedThreadPool()源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(5,
5,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
);
}
//newCachedThreadPool()源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()
);
}
2.ThreadPoolExecutor()本质
//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;
}
3.手写一个线程池
import java.util.concurrent.*;
public class Demo01 {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
// 最大任务承载量=Deque + max
// 超过最大任务承载量:RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用线程池来执行任务
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
四、四种拒绝策略
- new ThreadPoolExecutor.AbortPolicy():当达到最大任务承载量,如果仍有新的任务请求就会抛出RejectedExecutionException异常阻止系统正常运行
- new ThreadPoolExecutor.CallerRunsPolicy():当达到最大任务承载量,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量
- new ThreadPoolExecutor.DiscardPolicy():当达到最大任务承载量,直接丢弃任务,不予任何处理也不抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy():当达到最大任务承载量,抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务
五、阻塞队列
1. ArrayBlockingQueue :由数组结构组成的有界阻塞队列
2. LinkedBlockingQueue :由链表结构组成的有界阻塞队列
3. PriorityBlockingQueue :支持优先级排序的无界阻塞队列
4. DelayQueue:使用优先级队列实现的无界阻塞队列
5. SynchronousQueue:不存储元素的阻塞队列
6. LinkedTransferQueue:由链表结构组成的无界阻塞队列
7. LinkedBlockingDeque:由链表结构组成的双向阻塞队列
四组API
六、线程池的执行流程
1. 线程池刚创建时没有任何线程,任务队列是作为参数传进来的
2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b) 如果正在运行的线程数量等于 corePoolSize,那么将这个任务放入阻塞队列;
c) 如果这时候阻塞队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻执行这个任务;
d) 如果任务队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池根据创建线程池时选定的拒绝策略执行相应的流程
3. 当一个线程完成任务后,它会从阻塞队列中取下一个任务来执行
4. 当一个线程空闲超过一定的时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于 corePoolSize,那么这个线程就会被销毁。线程池完成所有任务完成后,最终会收缩到 corePoolSize 的大小
七、线程池的execute()和submit()方法的区别
1、接收参数不同
execute()只能接受Runnable类型参数,而submit()函数还可以接受Callable类型参数;
2.有无返回值
submit()函数有返回值Future,而execute()函数没有返回值;
3、submit()内部抛出的exception可以在外部感知并处理
通过调用submit()返回对象Future的get()函数可以判断任务是不是执行完成;如果在task里抛出了checked或者unchecked exception,调用者能够在外部通过Future.get捕获抛出的异常并做出及时的处理。
class RunnableTest implements Runnable{
private String taskName;
public RunnableTest(final String taskName){
this.taskName=taskName;
}
public void run(){
System.out.println("inside "+taskName);
throw new RuntimeException("exception from "+taskName);
}
}
public class submitVSexecute {
public static void main(String[] args) {
ExecutorService pool=Executors.newFixedThreadPool(2);
pool.execute(new RunnableTest("task1"));
Future task2 = pool.submit(new RunnableTest("task2"));
try{
if(task2.get()==null){
System.out.println("任务完成");
}
}catch(InterruptedException e){
}catch(ExecutionException e){
System.out.println(e.getCause().getMessage());
}
}
}