介绍
为什么要有线程池这个东西?
创建线程对象不像其他对象一样在JVM分配内存即可,还要调用操作系统内核的API,然后操作系统为线程分配一系列的资源,这个成本就很高了。所以线程是一个重量级对象,应该避免频繁创建和销毁
再说一下线程池的大概工作流程
以前我们运行线程的时候new Thread().start()即可,如果线程数多了,频繁的创建线程和销毁线程很费时间。
然后Doug Lea就这样设计了一下,预先启动几个线程,还准备好一个容器。每次想执行任务时,就将实现了Runnable接口的任务放到这个容器中,预先启动好的线程不断从容器中拿出任务,调用执行Runnable接口的run方法,这样刚开始启动的线程就能执行很多次任务。大概流程就是这样,真正的线程池考虑的东西比较多。
想到没有,这就是典型的生产者-消费者模式,线程池的使用者是生产者,线程池本身是消费者。用代码来实现一下
省略try catch版
public class MyThreadPool {
/** 利用阻塞队列实现生产者-消费者模式 */
BlockingQueue<Runnable> workQueue;
/** 保存内部工作线程 */
List<WorkThread> workThreadList = new ArrayList<>();
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
for (int i = 0; i < poolSize; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
workThreadList.add(workThread);
}
}
void execute(Runnable command) {
// 放入任务,如果没有空间,则阻塞等待
// try catch部分省略
workQueue.put(command);
}
class WorkThread extends Thread {
@Override
public void run() {
// 循环取任务并执行
while (true) {
Runnable task = null;
// 获取阻塞队列的第一个任务,并删除
// 如果没有元素,则会阻塞等待
// try catch部分省略
task = workQueue.take();
task.run();
}
}
}
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
MyThreadPool pool = new MyThreadPool(2, workQueue);
for (int i = 0; i < 10; i++) {
int num = i;
pool.execute(()->{
System.out.println("线程 " + num + " 执行");
});
}
}
}
可以正常工作
public class MyThreadPool {
/** 利用阻塞队列实现生产者-消费者模式 */
BlockingQueue<Runnable> workQueue;
/** 保存内部工作线程 */
List<WorkThread> workThreadList = new ArrayList<>();
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
for (int i = 0; i < poolSize; i++) {
WorkThread workThread = new WorkThread();
workThread.start();
workThreadList.add(workThread);
}
}
void execute(Runnable command) {
// 放入任务,如果没有空间,则阻塞等待
try {
workQueue.put(command);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class WorkThread extends Thread {
@Override
public void run() {
// 循环取任务并执行
while (true) {
Runnable task = null;
try {
// 获取阻塞队列的第一个任务,并删除
// 如果没有元素,则会阻塞等待
task = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
task.run();
}
}
}
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
MyThreadPool pool = new MyThreadPool(2, workQueue);
for (int i = 0; i < 10; i++) {
int num = i;
pool.execute(()->{
System.out.println("线程 " + num + " 执行");
});
}
}
}
来看一下Java中的线程池类ThreadPoolExecutor的构造函数有哪些参数?
public ThreadPoolExecutor
(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
来类比学习一下这些参数,我们把线程池类比为项目组,线程是这个项目组的成员
corePoolSize:线程池中最少的线程数,一个项目组总得有corePoolSize人坚守阵地。
maximumPoolSize:当项目很忙时,就得加人,请其他项目组的人来帮忙。但是公司空间有限,最多只能加到maximumPoolSize个人。当项目闲了,就得撤人了,最多能撤到corePoolSize个人
keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?如果一个线程在keepAliveTime(时间数字)* unit(时间单位)时间内都没有执行任务,说明这个线程很闲。如果此时线程数大于corePoolSize,这个线程就要被回收了
workQueue:就是任务队列
threadFactory:自定义如果创建线程,例如给线程指定一个有意义的名字
handler:workQueue满了(排期满了),再提交任务,该怎么处理呢?这个就是处理策略,线程池提供了4种策略,你也可以实现RejectedExecutionHandler接口来自定义策略
实现类 | 策略 |
---|---|
CallerRunsPolicy | 提交任务的线程自己去执行该任务 |
AbortPolicy | 默认的拒绝策略,会 throws RejectedExecutionException |
DiscardPolicy | 直接丢弃任务,没有任何异常抛出 |
DiscardOldestPolicy | 丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列 |