一、使用Executors创建线程池(阿里不推荐使用此方法)
Executors 是一个Java中的工具类。
常用方法:
1、newSingleThreadExecutor:创建一个单线程的线程池,用于需要保证顺序执行的逻辑,只有一个线程在执行。
2、newFixThreadExecutor:创建一个固定大小的线程池,用于已知并发压力下,对线程数做限制。
3、newCachedThreadPool:创建一个可以无限扩大的线程池,比较适合处理执行时间比较短的任务。
4、newScheduledThreadPool:创建一个可以延时启动,定时启动的线程池,用于需要多个后台线程执行周期任务的场景。
5、newWorkSteaLingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
关闭连接池方法:
1、shutdown():调用后不再接受新的任务
2、shutdownNow():调用后未开始的任务取消,执行中的任务停止执行,并返回未开始任务列表
总结:
1、Executors方法中使用的其实也是ThreadPoolExecutor创建的线程池。
2、以上方法都不支持自定义拒绝策略。
3、newSingThreadExecutor和newFixThreadExecutor,可能由于任务堆积,耗费内存变大甚至OOM
4、newCachedThreadPool和newScheduledThreadPool,可能会创建非常多的线程,导致OOM
二、使用ThreadPoolExecutor创建线程池(推荐)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
参数解释:
corePoolSize : 线程池核心池的大小。
maximumPoolSize : 线程池的最大线程数。
keepAliveTime : 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit : keepAliveTime 的时间单位。
workQueue : 用来储存等待执行任务的队列。
threadFactory : 线程工厂。(默认Executors.defaultThreadFactory())
handler 拒绝策略。
原理:
1、刚创建完成线程池后,此时线程池内线程数量为0。
2、有任务过来时,创建线程执行任务,线程数量一直达到corePoolSize 设置的大小。
3、任务数量继续增加时,就把任务放到等待执行队列workQueue 中,队列数量一直达到最大值(队列满了)。
4、任务数量继续增加,此时就又创建新的线程执行任务,线程数量一直达到maximumPoolSize 设置的大小。
5、任务数量继续增加,此时线程数量最大,队列也已满,则开始执行拒绝策略。
阻塞队列:
1、ArrayBlockingQueue(有界队列):基于数组的先进先出队列,创建时需指定大小。
2、LinkedBlockingQueue(无界队列):基于链表的先进先出队列,如果没有指定大小,则默认Integer.MAX_VALUE。
3、synchronousQueue(直接提交队列):不会保存提交的任务,而是直接新建线程来执行
拒绝策略:
1、ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExcetionException异常。(默认)
2、ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后执行新任务。
4、ThreadPoolExecutor.CallerRunPolicy:由调用线程处理该任务。
三、附上Demo
1、创建一个java线程池
package com.sumengnan.pool;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
public ThreadPoolExecutor threadPoolExecutor;
private static ThreadPool uniqueInstance = new ThreadPool();
private ThreadPool() {
threadPoolExecutor = new ThreadPoolExecutor(30,//线程池大小30(初始)
50, //默认最大线程50
5, //空闲线程保持时间
TimeUnit.SECONDS,//单位秒
new LinkedBlockingDeque<>(200),//,等待队列大小200
new ThreadPoolExecutor.DiscardOldestPolicy());//拒绝策略:丢弃队列最前面的任务,然后再添加任务
}
/**
* 获取实例
* @return
*/
public static ThreadPool getInstance() {
return uniqueInstance;
}
}
2、创建具体的任务逻辑
package com.sumengnan.test.web;
import com.sumengnan.SumengnanApplication;
public class AddThread implements Runnable {//,Callable<Object>
@Override
public void run() {
//打印次数
System.out.println("当前第"+ SumengnanApplication.i.incrementAndGet() +"次执行");
}
//如果需要关注返回结果,则需要实现Callable接口,否则仅仅实现Runnable接口就可以
/*@Override
public String call() throws Exception {
return "当前第"+ SumengnanApplication.i.incrementAndGet() +"次执行";
}*/
}
3、创建主类测试
package com.sumengnan;
import com.sumengnan.pool.ThreadPool;
import com.sumengnan.test.web.AddThread;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class SumengnanApplication {
//java并发机制中主要有三个特性:原子性、可见性和有序性。synchronized关键字只能保证可见性和有序性。
//volatile关键字保证了静态变量的可见性,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
//AtomicInteger类为了保证原子性,其中有一些incrementAndGet自增类的方法。
public static volatile AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) {
//循环230次
for (int i = 0; i <230; i++) {
System.out.println("第"+(i+1)+"个任务加入队列");
ThreadPool.getInstance().threadPoolExecutor.execute(new AddThread());
//如果需要关注返回结果,则需要使用submit方法,任务需实现Callable接口
/*Future<String> submit = ThreadPool.getInstance().threadPoolExecutor.submit((Callable<String>) new AddThread());
try {
System.out.println(submit.get());
} catch (Exception e) {
e.printStackTrace();
}*/
}
}
}
4、执行现象如下: