关于线程池的五种实现⽅式,七⼤参数,四种拒绝策略
⼀、池化技术之线程池
什么是池化技术?简单来说就是优化资源的使⽤,我准备好了⼀些资源,有⼈要⽤就到我这⾥拿,⽤完了就还给我。⽽⼀个⽐较重要的的实现就是线程池。那么线程池⽤到了池化技术有什么好处呢?
降低资源的消耗
志当存高远提⾼响应的速度
⽅便管理
也就是 线程复⽤、可以控制最⼤并发数、管理线程
⼆、线程池的五种实现⽅式
其实线程池我更愿意说成四种封装实现⽅式,⼀种原始实现⽅式。这四种封装的实现⽅式都是依赖于最原始的的实现⽅式。所以这⾥我们先介绍四种封装的实现⽅式
newSingleThreadExecutor()
这个线程池很有意思,说是线程池,但是池⼦⾥⾯只有⼀条线程。如果线程因为异常⽽停⽌,会⾃动新建⼀个线程补充。
我们可以测试⼀下:
我们对线程池执⾏⼗条打印任务,可以发现它们⽤的都是同⼀条线程
public static void test01() {
ExecutorService threadPool = wSingleThreadExecutor();
try {
//对线程进⾏执⾏⼗条打印任务
for(int i = 1; i <= 10; i++){
System.out.println(Thread.currentThread().getName()+"=>执⾏完毕!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//⽤完线程池⼀定要记得关闭
threadPool.shutdown();
}
}
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
newFixedThreadPool(指定线程数量)
这个线程池是可以指定我们的线程池⼤⼩的,可以针对我们具体的业务和情况来分配⼤⼩。它是创建⼀个核⼼线程数跟最⼤线程数相同的线程池,因此池中的线程数量既不会增加也不会变少,如果有空闲线程任务就会被执⾏,如果没有就放⼊任务队列,等待空闲线程。
我们同样来测试⼀下:
public static void test02() {
ExecutorService threadPool = wFixedThreadPool(5);
try {
//对线程进⾏执⾏⼗条打印任务
for(int i = 1; i <= 10; i++){
System.out.println(Thread.currentThread().getName()+"=>执⾏完毕!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
东州兵/
/⽤完线程池⼀定要记得关闭
threadPool.shutdown();
}
}
我们创建了五条线程的线程池,在打印任务的时候,可以发现线程都有进⾏⼯作
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-1=>执⾏完毕!
pool-1-thread-2=>执⾏完毕!
pool-1-thread-3=>执⾏完毕!
pool-1-thread-5=>执⾏完毕!
pool-1-thread-4=>执⾏完毕!
newCachedThreadPool()
这个线程池是创建⼀个核⼼线程数为0,最⼤线程为Inter.MAX_VALUE的线程池,也就是说没有限制,线程池中的线程数量不确定,但如果有空闲线程可以复⽤,则优先使⽤,如果没有空闲线程,则创建新线程处理任务,处理完放⼊线程池。
我们同样来测试⼀下
newScheduledThreadPool(指定最⼤线程数量)
创建⼀个没有最⼤线程数限制的可以定时执⾏线程池
在这⾥,还有创建⼀个只有单个线程的可以定时执⾏线程池(wSingleThreadScheduledExecutor())这些都是上⾯的线程池扩展开来了,不详细介绍了。
三、介绍线程池的七⼤参数
上⾯我们也说到了线程池有五种实现⽅式,但是实际上我们就介绍了四种。那么最后⼀种是什么呢?不急,我们可以点开我们上⾯线程池实现⽅式的源码进⾏查看,可以发现
newSingleThreadExecutor()的实现源码
⽽点开其他⼏个线程池到最后都可以发现,他们实际上⽤的就是这个ThreadPoolExecutor。我们把源代码粘过来分析,其实也就是这七⼤参数 /**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is t
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to u for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to u when the executor
* creates a new thread
* @param handler the handler to u when execution is blocked
钱币石* becau the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
十送红军歌词throw new NullPointerException();
this.acc = SecurityManager() == null ?
null :
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = Nanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
毫⽆悬念,这就是最后⼀种⽅式,也是其他实现⽅式的基础。⽽⽤这种⽅式也是最容易控制,因为我们可以⾃由的设置参数。在阿⾥巴巴开发⼿册中也提到了
所以我们更需要去了解这七⼤参数,在平时⽤线程池的时候尽量去⽤ThreadPoolExecutor。⽽关于这七⼤参数我们简单概括就是
corePoolSize: 线程池核⼼线程个数
美色商城
workQueue: ⽤于保存等待执⾏任务的阻塞队列
maximunPoolSize: 线程池最⼤线程数量
ThreadFactory: 创建线程的⼯⼚
针怎么画RejectedExecutionHandler: 队列满,并且线程达到最⼤线程数量的时候,对新任务的处理策略
keeyAliveTime: 空闲线程存活时间
TimeUnit: 存活时间单位
⽽关于线程池最⼤线程数量,我们也有两种设置⽅式
1. CPU密集型
获得cpu的核数,不同的硬件不⼀样,设置核数的的线程数量。
我们可以通过代码 Runtime().availableProcessors();获取,然后设置。
2. IO密集型
IO⾮常消耗资源,所有我们需要计算⼤型的IO程序任务有多少个。
⼀般来说,线程池最⼤值 > ⼤型任务的数量即可
⼀般设置⼤型任务的数量*2
这⾥我们⽤⼀个例⼦可以更好理解这些参数在线程池⾥⾯的位置和作⽤。
如图,我们这是⼀个银⾏
我们⼀共有五个柜台,可以理解为线程池的最⼤线程数量,⽽其中有两个是在营业中,可以理解为线程池核⼼线程个数。⽽下⾯的等待厅可以理解为⽤于保存等
待执⾏任务的阻塞队列。银⾏就是创建线程的⼯⼚。
⽽关于空闲线程存活时间,我们可以理解为如下图这种情况,当五个营业中,却只有两个⼈需要被服务,⽽其他三个⼈⼀直处于等待的情况下,等了⼀个⼩时了,他们被通知下班了。这⼀个⼩时时间就可以说是空闲线程存活时间,⽽存活时间单位,顾名思义。
到现在我们就剩⼀个拒绝策略还没介绍,什么是拒绝策略呢?我们可以假设当银⾏五个柜台都有⼈在被服务,如下图。⽽等待厅这个时候也是充满了⼈,银⾏实在容不下⼈了。
这个时候对银⾏外⾯那个等待的⼈的处理策略就是拒绝策略。
我们同样了解之后⽤代码来测试⼀下:
public static void test05(){处女座双鱼座
ExecutorService threadPool = new ThreadPoolExecutor(
中国贪污//核⼼线程数量
2,
//最⼤线程数量
5,
//空闲线程存活时间
3,
//存活单位
TimeUnit.SECONDS,
//这⾥我们使⽤⼤多数线程池都默认使⽤的阻塞队列,并使容量为3