1. CPU与线程之间的关系
2. 为什么要使用线程线程池
3. 线程池的核心思想(工作模型)
4. JDK的原生线程池及参数说明
5. Executors中的线程池的工厂方法
6. 自定义线程池及使用案例
7. 讨论:使用线程池之后是否需要主动关闭?
1.CPU与线程之间的关系
线程是CPU调度和分派的基本单位。
更多介绍参考:https://www.cnblogs.com/fubaizhaizhuren/p/7501403.html
CPU与缓存和内存之间关系
CPU与线程之间关系
线程读写内存的流程
2.为什么要使用线程线程池
线程池技术就是为了解决服务器频繁在创建和销毁线程所花费过多的时间和系统资源(服务器在创建和销毁线程所花费的时间和系统资源可能比处理客户端请求处理的任务花费的时间和资源更多);
合理的使用线程池便可重复利用已创建的线程,以减少在创建线程和销毁线程上花费的时间和资源。
除此之外,线程池在某些情况下还能动态的调整工作线程的数量,以平衡资源消耗和工作效率。
同时线程池还提供了对池中工作线程进行统一的管理的相关方法。
3.线程池的核心思想(工作模型)
线程池的工作模型主要两部分组成,一部分是运行Runnable的Thread对象,另一部分就是阻塞队列。
由线程池创建的Thread对象其内部的run方法会通过阻塞队列的take方法获取一个Runnable对象,然后执行这个Runnable对象的run方法(即,在Thread的run方法中调用Runnable对象的run方法)。当Runnable对象的run方法执行完毕以后,Thread中的run方法又循环的从阻塞队列中获取下一个Runnable对象继续执行。这样就实现了Thread对象的重复利用,也就减少了创建线程和销毁线程所消耗的资源。
当需要向线程池提交任务时会调用阻塞队列的offer方法向队列的尾部添加任务。提交的任务实际上就是是Runnable对象或Callable对象。
4.JDK的原生线程池及参数说明
原生ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
if (var6 != null && var7 != null && var8 != null) {
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = var1;
this.maximumPoolSize = var2;
this.workQueue = var6;
this.keepAliveTime = var5.toNanos(var3);
this.threadFactory = var7;
this.handler = var8;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
核心参数说明
corePoolSize:线程池中核心线程数的最大值
maximumPoolSize:线程池中能拥有最多线程数
workQueue:用于缓存任务的阻塞队列
keepAliveTime:表示空闲线程的存活时间
TimeUnitunit:表示keepAliveTime的单位。
handler:表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略。
threadFactory:指定创建线程的工厂
各个参数之间的关系
现在通过向线程池添加新的任务来说明着三者之间的关系。
(1)如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。
(2)如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
(3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
(4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
注意,线程池并没有标记哪个线程是核心线程,哪个是非核心线程,线程池只关心核心线程的数量。
通俗解释,如果把线程池比作一个单位的话,corePoolSize就表示正式工,线程就可以表示一个员工。当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是干不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是workQueue,等正式工完成了手上的工作,就到这里来取新的任务。如果不巧,年末了,各个部门都向这个单位委派任务,导致workQueue已经没有空位置放新的任务,于是单位决定招点临时工吧(临时工:又是我!)。临时工也不是想招多少就找多少,上级部门通过这个单位的maximumPoolSize确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize–corePoolSize个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。
场景1:假设线程池这个单位已经招了些临时工,但新任务没有继续增加,所以随着每个员工忙完手头的工作,都来workQueue领取新的任务(看看这个单位的员工多自觉啊)。随着各个员工齐心协力,任务越来越少,员工数没变,那么就必定有闲着没事干的员工。这样的话领导不乐意啦,但是又不能轻易fire没事干的员工,因为随时可能有新任务来,于是领导想了个办法,设定了keepAliveTime,当空闲的员工在keepAliveTime这段时间还没有找到事情干,就被辞退啦,毕竟地主家也没有余粮啊!当然辞退到corePoolSize个员工时就不再辞退了,领导也不想当光杆司令啊!
场景2:假设线程池这个单位招满临时工,但新任务依然继续增加,线程池从上到下,从里到外真心忙的不可开交,阻塞队列也满了,只好拒绝上级委派下来的任务。怎么拒绝是门艺术,handler一般可以采取以下四种取值。
ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务(最先提交而没有得到执行的任务)
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
线程池常用的阻塞队列
(1)SynchronousQueue<Runnable>():此队列中不缓存任何一个任务。向线程池提交任务时,如果没有空闲线程来运行任务,则入列操作会阻塞。当有线程来获取任务时,出列操作会唤醒执行入列操作的线程。从这个特性来看,SynchronousQueue是一个无界队列,因此当使用SynchronousQueue作为线程池的阻塞队列时,参数maximumPoolSizes没有任何作用。
(2)LinkedBlockingQueue<Runnable>():顾名思义是用链表实现的队列,可以是有界的,也可以是无界的,但在Executors中默认使用无界的。
(3)ArrayBlockingQueue<Runnable>(100):使用数组实现的队列,有界
5.Executors中的线程池的工厂方法
Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool:使用SynchronousQueue作为阻塞队列,队列无界,线程的空闲时限为60秒。这种类型的线程池非常适用IO密集的服务,因为IO请求具有密集、数量巨大、不持续、服务器端CPU等待IO响应时间长的特点。服务器端为了能提高CPU的使用率就应该为每个IO请求都创建一个线程,以免CPU因为等待IO响应而空闲
Executors.newFixedThreadPool(10)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool:需指定核心线程数,核心线程数和最大线程数相同,使用LinkedBlockingQueue 作为阻塞队列,队列无界,线程空闲时间0秒。这种类型的线程池可以适用CPU密集的工作,在这种工作中CPU忙于计算而很少空闲,由于CPU能真正并发的执行的线程数是一定的(比如四核八线程),所以对于那些需要CPU进行大量计算的线程,创建的线程数超过CPU能够真正并发执行的线程数就没有太大的意义。
Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newSingleThreadExecutor:池中只有一个线程工作,阻塞队列无界,它能保证按照任务提交的顺序来执行任务
6.自定义线程池及使用案例
案例1:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池管理工具类
* @author 14043094
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class SingleThreadFactory implements ThreadFactory {
static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
final ThreadGroup group;
final AtomicInteger threadNumber = new AtomicInteger(1);
final String namePrefix;
/**
* 新建一个线程池
*/
private static ExecutorService executorService = Executors.newCachedThreadPool(new SingleThreadFactory(
"-async-thread-"));
/**
*
* 功能描述:
* 〈功能详细描述〉
*
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public static ExecutorService getExecutorService() {
return executorService;
}
/**
*
* @param threadName
*/
public SingleThreadFactory(String threadName) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + threadName;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
if (t.getPriority() != Thread.MIN_PRIORITY) {
t.setPriority(Thread.MIN_PRIORITY);
}
return t;
}
}
使用案例
//异步处理群发消息
SingleThreadFactory.getExecutorService().execute(new Runnable() {
@Override
public void run() {
groupMsgBatchSendService.platformBatchSend(sendMsg, appId, appSecret);
}
});
案例2:
package com.suning.o2o.future;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Auther: Tian.shujian 18066629
* @Date: 2018/8/15 17:58
* @Description: 线程池工厂类
*/
public class ThreadPoolExecutorFactory {
/**
* corePoolSize 池中所保存的线程数,包括空闲线程。
*/
private static int corePoolSize = 4;
/**
* maximumPoolSize - 池中允许的最大线程数。
*/
private static int maximumPoolSize = 80;
/**
* keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,线程池维护线程所允许的空闲时间
*/
private static final int keepAliveTime = 60;
/**
* 执行前用于保持任务的队列(缓冲队列)
*/
private static final int capacity = 300;
/**
* 线程工厂
*/
private static ThreadFactory threadFactory;
/**
* 线程池对象
*/
private static ThreadPoolExecutor threadPoolExecutor;
/**
* 队列
*/
private static BlockingQueue<Runnable> blockingQueue;
/**
* 拒绝策略
*/
private static RejectedExecutionHandler handler;
/**
* 时间单位
*/
private static TimeUnit unit;
static{
corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
maximumPoolSize = corePoolSize * 10;
handler = new ThreadPoolExecutor.CallerRunsPolicy();
blockingQueue = new ArrayBlockingQueue<Runnable>(capacity);
unit = TimeUnit.SECONDS;
threadFactory = new ThreadFactory() {
final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable,"o2owxapp_thread #" + mCount.getAndIncrement());
}
};
}
//构造方法私有化
private ThreadPoolExecutorFactory(){}
public static ThreadPoolExecutor getThreadPoolExecutor(){
if(null == threadPoolExecutor){
ThreadPoolExecutor t;
synchronized (ThreadPoolExecutor.class) {
t = threadPoolExecutor;
if(null == t){
synchronized (ThreadPoolExecutor.class) {
t = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
blockingQueue,
threadFactory,
handler
);
}
threadPoolExecutor = t;
}
}
}
return threadPoolExecutor;
}
}
7.讨论:使用线程池之后是否需要主动关闭?
后续更新
参考文章:
线程池的工作原理及使用示例
线程池ThreadPoolExecutor、Executors参数详解与源代码分析
根据CPU核心数确定线程池并发线程数
多线程和CPU的关系