知识体系驱动即一条道走到头的学习(逮住某个专题深入研究,直到整个过一遍)
项目驱动即以完成项目为目的,中间需要用到什么再去研究什么
什么是线程池
线程池的处理流程
保存待执行任务的阻塞队列
创建自己的线程池
JDK 提供的线程池及使用场景
1.newFixedThreadPool
2.newSingleThreadExecutor
3.newCachedThreadPool
4.newScheduledThreadPool
两种提交任务的方法
execute
submit
关闭线程池
如何合理地选择或者配置
总结
什么是线程池
检查对应的类是否已经被加载、解析和初始化
类加载后,为新生对象分配内存
将分配到的内存空间初始为 0
对对象进行关键信息的设置,比如对象的哈希码等
然后执行 init 方法初始化对象
线程池的处理流程
public ThreadPoolExecutor(int corePoolSize, //核心线程的数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
TimeUnit unit, //存活时间的单位
BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
ThreadFactory threadFactory, //创建新线程使用的工厂
RejectedExecutionHandler handler // 当任务无法执行时的处理器
) {...}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1.当前池中线程比核心数少,新建一个线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.核心池已满,但任务队列未满,添加到队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //如果这时被关闭了,拒绝任务
reject(command);
else if (workerCountOf(recheck) == 0) //如果之前的线程已被销毁完,新建一个线程
addWorker(null, false);
}
//3.核心池已满,队列已满,试着创建一个新线程
else if (!addWorker(command, false))
reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}
有了新需求,先看核心员工数量超没超出最大核心员工数,还有名额的话就新招一个核心员工来做
需要获取全局锁
核心员工已经最多了,HR 不给批 HC 了,那这个需求只好攒着,放到待完成任务列表吧
如果列表已经堆满了,核心员工基本没机会搞完这么多任务了,那就找个外包吧
需要获取全局锁
如果核心员工 + 外包员工的数量已经是团队最多能承受人数了,没办法,这个需求接不了了
corePoolSize
:核心线程池数量在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干
等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了
maximumPoolSize
:最大线程数量包括核心线程池数量 + 核心以外的数量
如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务
keepAliveTime
:核心池以外的线程存活时间,即没有任务的外包的存活时间如果给线程池设置
allowCoreThreadTimeOut(true)
,则核心线程在空闲时头上也会响起死亡的倒计时如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务
workQueue
:保存待执行任务的阻塞队列不同的任务类型有不同的选择,下一小节介绍
threadFactory
:每个线程创建的地方可以给线程起个好听的名字,设置个优先级啥的
handler
:饱和策略,大家都很忙,咋办呢,有四种策略CallerRunsPolicy
:只要线程池没关闭,就直接用调用者所在线程来运行任务AbortPolicy
:直接抛出RejectedExecutionException
异常DiscardPolicy
:悄悄把任务放生,不做了DiscardOldestPolicy
:把队列里待最久的那个任务扔了,然后再调用execute()
试试看能行不我们也可以实现自己的
RejectedExecutionHandler
接口自定义策略,比如如记录日志什么的
保存待执行任务的阻塞队列
ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素
吞吐量通常要高于 ArrayBlockingQueue
Executors.newFixedThreadPool() 使用了这个队列
SynchronousQueue:不存储元素的阻塞队列
每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
吞吐量通常要高于 LinkedBlockingQueue
Executors.newCachedThreadPool使用了这个队列
PriorityBlockingQueue:具有优先级的、无限阻塞队列
创建自己的线程池
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU 数*2 private static final int MAXIMUM_POOL_SIZE = 64; // 线程池最大线程数 private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒
设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程
最大线程数设置为 64
空闲线程的存活时间设置为 1 秒
private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128);
private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement()); thread.setPriority(Thread.NORM_PRIORITY); return thread; } };
private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY, new ThreadPoolExecutor.DiscardOldestPolicy());
public class ThreadPoolManager { private final String TAG = this.getClass().getSimpleName(); private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU数*2 private static final int MAXIMUM_POOL_SIZE = 64; // 线程队列最大线程数 private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒 private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128); private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement()); thread.setPriority(Thread.NORM_PRIORITY); return thread; } }; private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY, new ThreadPoolExecutor.DiscardOldestPolicy()); private static volatile ThreadPoolManager mInstance = new ThreadPoolManager(); public static ThreadPoolManager getInstance() { return mInstance; } public void addTask(Runnable runnable) { mExecutor.execute(runnable); } @Deprecated public void shutdownNow() { mExecutor.shutdownNow(); } }
JDK 提供的线程池及使用场景
1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
线程数等于核心线程数后,将任务加入阻塞队列
由于队列容量非常大,可以一直加加加
执行完任务的线程反复去队列中取任务执行
2.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
线程池中没有线程时,新建一个线程执行任务
有一个线程以后,将任务加入阻塞队列,不停加加加
唯一的这一个线程不停地去队列里取任务执行
3.newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
没有核心线程,直接向
SynchronousQueue
中提交任务如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜
4.newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); } private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
scheduleAtFixedRate()
:按某种速率周期执行scheduleWithFixedDelay()
:在某个延迟后执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period), sequencer.getAndIncrement()); RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t; } public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), -unit.toNanos(delay), sequencer.getAndIncrement()); RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t; }
private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> { //添加到队列中的顺序 private final long sequenceNumber; //何时执行这个任务 private volatile long time; //执行的间隔周期 private final long period; //实际被添加到队列中的 task RunnableScheduledFuture<V> outerTask = this; //在 delay queue 中的索引,便于取消时快速查找 int heapIndex; //... }
调用上面两个方法添加一个任务
线程池中的线程从 DelayQueue 中取任务
然后执行任务
线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask
DelayQueue.take()
执行完后修改这个 task 的 time 为下次被执行的时间
然后再把这个 task 放回队列中
DelayQueue.add()
两种提交任务的方法
execute()
:提交不需要返回值的任务submit()
:提交需要返回值的任务
execute
void execute(Runnable command);
ExecutorService executor = Executors.newCachedThreadPool(); executor.execute(new Runnable() { @Override public void run() { //do something } });
submit
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
FutureTask futureTask = new FutureTask(new Callable<String>() { //创建 Callable 任务 @Override public String call() throws Exception { String result = ""; //do something return result; } }); Future<?> submit = executor.submit(futureTask); //提交到线程池 try { Object result = submit.get(); //获取结果 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
关闭线程池
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); //获取权限 advanceRunState(SHUTDOWN); //修改运行状态 interruptIdleWorkers(); //遍历停止未开启的线程 onShutdown(); // 目前空实现 } finally { mainLock.unlock(); } tryTerminate(); } private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { //遍历所有线程 Thread t = w.thread; //多了一个条件w.tryLock(),表示拿到锁后就中断 if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(STOP); //修改运行状态 interruptWorkers(); //中断所有 tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; } private void interruptWorkers() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) //中断全部线程,不管是否在执行 w.interruptIfStarted(); } finally { mainLock.unlock(); } }
如何合理地选择或者配置
CachedThreadPool
用于并发执行大量短期的小任务,或者是负载较轻的服务器。FixedThreadPool
用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。SingleThreadExecutor
用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。ScheduledThreadPoolExecutor
用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。
总结