对多线程熟悉吗,来谈谈线程池的好处?
线程池的好处
线程池的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
控制线程池的并发数
并发:在某个时间段内,多个程序都处在执行和执行完毕之间;但在一个时间点上只有一个程序在运行。头脑风暴:老鹰妈妈喂小雏鹰食物,小雏鹰很多,而老鹰只有一张嘴,她需要一个个喂过去,到最后每个小雏鹰都可以吃到,但是在一个时间点里只能有一个小雏鹰可以吃到美味的食物。
并行:在某个时间段里,每个程序按照自己独立异步的速度执行,程序之间互不干扰。头脑风暴:这就好似老鹰妈妈决定这样喂食太费劲,于是为每个小雏鹰请了个保姆,这样子在一个时间点里,每个小雏鹰都可以同时吃到食物,而且互相不干扰。
回到线程池,控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞。头脑风暴:还是拿老鹰的例子来讲,妈妈只有一个,要这么一个个喂下去,一些饿坏的小雏鹰等不下去了就要破坏规则,抢在靠前喂食的雏鹰面前,而前面的雏鹰也不是吃软饭的,于是打起来了,场面混乱。老鹰生气了,这么不懂事,谁也别吃了,于是造成了最后谁也没食吃的局面。
线程池可以对线程进行管理
线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
线程池的详解
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这里是7个参数(我们在开发中用的更多的是5个参数的构造方法),OK,那我们来看看这里七个参数的含义:
- corePoolSize 线程池中核心线程的数量
- maximumPoolSize 线程池中最大线程数量
- keepAliveTime 非核心线程的存活时间,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的存活时间。
- unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
- workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
- threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可
- handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
这7个参数中,平常最多用到的是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue。在这里我主要对corePoolSize、maximumPoolSize和workQueue三个参数进行详解。
maximumPoolSize(最大线程数) = corePoolSize(核心线程数) + noCorePoolSize(非核心线程数)
(1) 当currentSize<corePoolSize时,直接启动一个核心线程并执行任务。
(2) 当currentSize>=corePoolSize,并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。
(3) 当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。
(4) 当currentSize>=corePoolSize,workQueue已满,并且currentSize>maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。
其他线程池
- FixedThreadPool:Fixed中文解释为固定。结合在一起解释固定的线程池,说的更全面点就是,有固定数量线程的线程池。其corePoolSize=maximumPoolSize,且keepAliveTime为0,适合线程稳定的场所。
- SingleThreadPool:Single中文解释为单一。结合在一起解释单一的线程池,说的更全面点就是,有固定数量线程的线程池,且数量为一,从数学的角度来看SingleThreadPool应该属于FixedThreadPool的子集。其corePoolSize=maximumPoolSize=1,且keepAliveTime为0,适合线程同步操作的场所。注:线程如果死了,会创建一个新的线程进行补充,即不会出现没有线程的情况。
- CachedThreadPool:Cached中文解释为储存。结合在一起解释储存的线程池,说的更通俗易懂,既然要储存,其容量肯定是很大,所以他的corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE(2^32-1一个很大的数字)
- ScheduledThreadPool:Scheduled中文解释为计划。结合在一起解释计划的线程池,顾名思义既然涉及到计划,必然会涉及到时间。所以ScheduledThreadPool是一个具有定时定期执行任务功能的线程池。
线程池的单例
单例
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意事项:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
线程池的单例
那么问题来了,我线程池用的好好的,用的时候创建一个,不用就不管他,那为什么要将线程池设计成单例模式呢。那么就要看看你将线程池应用的场所了。一般情况下,整个系统中只需要单种线程池,多个线程公用一个线程池,不会是每创一个线程就要创建一个线程池,那样子你还不如不用线程池呢。
首先在ThreadPool类里面实现线程池的创建,我们这里创建的是FixedThreadPool线程池(记住构造方法要私有,保证不被其他类实例化)
private ThreadPool(int corepoolsize, int maximumpoolsize, long keepalivetime){
this.corepoolsize = corepoolsize;
this.maximumpoolsize = maximumpoolsize;
this.keepalivetime = keepalivetime;
}
public void executor(Runnable runnable){
if (runnable == null){
return;
}
if (mexecutor == null){
mexecutor = new ThreadPoolExecutor(corepoolsize, //核心线程数
maximumpoolsize, //最大线程数
keepalivetime, //闲置线程存活时间
TimeUnit.MILLISECONDS, // 时间单位
new LinkedBlockingDeque<Runnable>(), //线程队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy() //队列已满,而且当前线程数已经超过最大线程数时的异常处理策略
);
}
mexecutor.execute(runnable);
}
再然后对ThreadPool内部类,在类里面对它实例化,实现单例
// 获取单例的线程池对象
public static ThreadPool getThreadPool() {
if (mThreadPool == null) {
synchronized (ThreadPool.class) {
if (mThreadPool == null) {
int cpuNum = Runtime.getRuntime().availableProcessors();// 获取处理器数量
int threadNum = cpuNum * 2 + 1;// 根据cpu数量,计算出合理的线程并发数
mThreadPool = new ThreadPool(threadNum, threadNum, 0L);
}
}
}
return mThreadPool;
}
参考: |