线程池
1.线程池总结
- 线程池的好处:线程复用,控制最大并发数,管理线程
2.阻塞队列和非阻塞队列
- 非阻塞队列 和 阻塞队列 的入队(offer ) 和出队(poll)
// 非阻塞队列
public interface Queue<E> extends Collection<E> {
boolean offer(E e);
E poll();
}
// 阻塞队列
public interface BlockingQueue<E> extends Queue<E> {
boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;
E poll(long timeout, TimeUnit unit) throws InterruptedException;
}
- 入队:
- 非阻塞队列:队列满,添加元素立马返回失败
- 阻塞队列:添加元素,如果队列满,可以等待一段时间,如果在等待的过程中,队列有空闲位置就添加成功
- 出队:
- 非阻塞队列:队列为空,poll出队立马返回null
- 阻塞队列:poll出队,如果队列为空,可以等待一段时间,等待期间出现新元素,那么就出队成功。
//==============================非阻塞队列===============================
ConcurrentLinkedQueue<String> clq = new ConcurrentLinkedQueue<>();
//入队
clq.add("a");
clq.add("b");
clq.add("c");
//查看队列头
String peek = clq.peek();
System.out.println("peek = " + peek);//a
System.out.println("clq.size() = " + clq.size());//3
//出队
String poll = clq.poll();
System.out.println("poll = " + poll);//a
System.out.println("clq.size() = " + clq.size());//2
System.out.println("=================================");
//======================阻塞队列=======================================
BlockingQueue<String> bq = new LinkedBlockingDeque<>(2);
// bq.add("a");
//bq.add("b");
// bq.add("c");//java.lang.IllegalStateException: Deque full
//todo:offer 和 add一样都是入队,但是offer可以等待一定时间看能否可以插入
bq.offer("a");
bq.offer("b");
System.out.println("bq.poll() = " + bq.poll());
//阻塞3秒,看能不能把c放进队列中
bq.offer("c",3, TimeUnit.SECONDS);
System.out.println("bq.poll() = " + bq.poll());
System.out.println("bq.poll() = " + bq.poll());
//阻塞5秒,等待是否有可被取出的元素
System.out.println("bq.poll() = " + bq.poll(5,TimeUnit.SECONDS));
2.线程池原理
- 线程池ThreadPoolExecutor七大构造参数
参数 | 说明 |
---|---|
corePollSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列,存储待执行的任务 |
threadFactory | 用于创建线程,是生成线程池中工作线程的工厂 |
handler | 拒绝策略,任务阻塞队列满且工作线程数达到maximumPoolSize触发拒绝策略 |
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- ThreadPoolExecutor提供了四种拒绝策略:
饱和策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常(默认策略) |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程处理该任务 |
自定义 | 可以通过实现RejectedExecutionHandler接口自定义处理方式 |
- 线程池工作原理
3.线程池的状态和状态之间的切换?
- 线程池状态
- RUNNING:接收新任务,处理排队任务
- SHUTDOWN:不接收新任务,但是处理排队任务
- STOP:不接收新任务,也不处理排队任务,并且中断正在进行的任务
- TIDYING:所有任务都已经终止,线程池中工作线程为0,线程会转换为TIDYING状态,即将运行钩子方法 terminated()
- TERMINATED:terminated()方法执行完成时进入TERMINATED状态
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don't accept new tasks, but process queued tasks
STOP: Don't accept new tasks, don't process queued tasks,
and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero,
the thread transitioning to state TIDYING
will run the terminated() hook method
TERMINATED: terminated() has completed
- 线程池状态转换
RUNNING -> SHUTDOWN
On invocation of shutdown(), perhaps implicitly in finalize()
(RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow()
SHUTDOWN -> TIDYING
When both queue and pool are empty
STOP -> TIDYING
When pool is empty
TIDYING -> TERMINATED
When the terminated() hook method has completed
4.线程池有哪些工作队列?
-
ArrayBlockingQueue:基于数组结构的有界阻塞队列(FIFO)。
-
LinkedBlockingQueue:基于链表结构的无界阻塞队列(FIFO)。
-
SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
-
DelayedWorkQueue:具有优先级的延迟无界阻塞队列。可以根据任务自身的优先级顺序延迟执行。
- 为什么要使用DelayedWorkQueue呢?
- 定时任务执行时需要取出最近要执行的任务,所以任务在队列中每次出队时一定要是当前队列中执行时间最靠前的,所以自然要使用优先级队列。
- 为什么要使用DelayedWorkQueue呢?
-
Executors工具类
// 可缓存
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 定长
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 单例
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 定时 来自 ScheduledThreadPoolExecutor extends ThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
5.项目中怎么创建线程池?
- 使用ThreadPoolExecutor的原生构造函数创建,这样可以更加明确每项参数的意义,不使用Executors工具类创建。
- FixedThreadPool 和 SingleThreadPool:
- 允许的任务队列(LinkedBlockingQueue)长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool:
- 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
6.如何合理设置线程池参数?
7.线程池模拟分段抓取Mysql数据
public static void main(String[] args){
xxx(13000);
}
// CompletableFuture
// long selectCount = 13000;//假设 为 select count(*) from t 的返回结果
public static void xxx(long selectCount){
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
20,
20,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
new ThreadPoolExecutor.AbortPolicy());
class RUN implements Runnable{
private String sql;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" ~ "+ sql);
}
public RUN(String sql) {
this.sql = sql;
}
}
// 模拟从数据库分段抓取数据 同步到ES
long step = 2000;// 每次处理2000条数据
long rs = selectCount / step;
if (rs==0){
CompletableFuture.runAsync(new RUN("select * from t limit 0,"+selectCount),executor);
}else {
long v = selectCount % step;
if (v != 0){
rs++;
}
long limitHead = 0;
for (long i = 0; i < rs; i++) {
CompletableFuture.runAsync(new RUN("select * from t limit "+limitHead+","+step),executor);
limitHead+=2000;
}
}
}
pool-1-thread-1 ~ select * from t limit 0,2000
pool-1-thread-4 ~ select * from t limit 6000,2000
pool-1-thread-3 ~ select * from t limit 4000,2000
pool-1-thread-6 ~ select * from t limit 10000,2000
pool-1-thread-5 ~ select * from t limit 8000,2000
pool-1-thread-7 ~ select * from t limit 12000,2000
pool-1-thread-2 ~ select * from t limit 2000,2000