其实这篇文章在去年就已经写完了,但是因为懒一直没有整理成博客,今天痛定思痛下定决心,也要把它发表出来!
好吧,事实上电脑里还存了这么多东西没有整理成博客,我真是服了自己这个拖延症了!
废话不多说,进入正题。
了解程序的设计理念,才能更好的理解他的设计思路。
目前JVM采用的线程模型还是1:1的模型,也就是说一条Java线程对应一条OS线程,那么无论是创建,销毁,还是阻塞唤醒线程,其实都涉及到线程上下文的切换。 也就是说无论是线程中存在的局部变量,线程中程序运行的位置指针,都需要被操作系统精心保存起来,虽然说线程相比较进程来说,已经极大的减少了这些资源的占用,但是比如像早期BIO实现服务器模型那种(如下图),一个客户端连接进来就要开启一条线程,会极大的占用系统资源,降低使用效率。
因此大神们就相想出一个解决方案,与其等待任务进来,不断地创建再销毁线程,不如维护几个常用线程,然后提交任务进来,让这几个常用线程干活,这样就能极大的减少线程创建和切换时的资源开销。 于是这时候服务器就可以这样实现,不用在像以前一样创建成百上千的线程。
这就是线程池的核心设计理念,核心理念就是为了减少减少创建/切换线程开销。
这张图就是Java默认实现线程池的架构图,现在看可能还是让人一头雾水,这里我分模块来介绍。
首先要明白什么是BlockingQueue
,即阻塞队列,也就是一个能保证读写线程安全的队列容器, 其原理也很简单,就是使用到了生产者消费者模型 和 线程同步机制,保证了多线程去操作容器时,会变并行为串行操作。
在线程池中的作用,就是作为一个任务存储队列,因为线程池中有多条线程同时去队列中获取任务,因此需要调用其同步获取方法take()
。
corePool
就是核心线程池,线程池初始化时会先创建几条线程,这几条线程就是核心线程池的线程。
核心线程池相当于线程池的长期工人,线程池创建他们就已经存在,直到线程池销毁他们才会销毁。
线程池有一种冗余机制,就是当核心线程池忙不过来,而且BlockingQueue
满了(满了以后就不能再继续向队列里添加任务),这时线程池就会找一些临时工, 这就是corePool
之外的线程,当然maxmumPool
也会有上限,当超过这个上限线程池就拒绝工作啦!
说明一点,为什么说maxmumPool中的是临时工,因为他们在一定时间内没有工作以后,线程池会解雇他们,即销毁核心线程池以外的线程。
上面说到,超过maxmumPool
以后,线程池会拒绝工作,这个就是线程池的拒绝策略,也就是说拒绝工作以后怎么处理再提交上来的任务。
下面是我的一段笔记:
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
这个是线程池的核心方法,也是线程池的入口方法,相当于一个核心调度器,来决定提交的任务进入哪个流程。后续我们会对这个方法进行源码分析。
public class TestThreadPool {
static class MyTask implements Runnable {
@Override
public void run() {
System.out.println("任务运行在线程:" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
// 创建一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,20,1000,
TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
for (int i = 0; i <100; i++) {
// 提交任务
threadPool.execute(new MyTask());
}
// 关闭线程池
threadPool.shutdown();
}
}
上面是一段线程池的使用实例,我们创建了一个线程池,然后循环提交了100个任务,然后关闭线程池。可以看到整个使用还是比较简单的。
下面是构造方法,这里我们推荐不使用JDK自带的几种线程池,因为自己进行配置会具有更好的可控性(《阿里巴巴编码规范》中的推荐写法)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {...}
上面几个参数说明:参考上面线程池模型中解释。
corePoolSize
,就直接创建一个核心池线程,执行任务corePoolSize
,就直接提交到BlockQueue
任务队列中,此时线程池中的线程会不断从任务队列中取出任务执行maximumPoolSize
,当然新建的线程也会不断从任务队列中取任务maximumPoolSize
,再提交的任务会进入拒绝策略下一篇:线程池源码分析
图片素材来源: