这一期给我大家带来线程池,详细的聊一聊线程池,线程池在大厂的面试中也是高频率被问到。
而且,如果说你的简历上有写着线程池的使用的案例场景以及调优的经验,基本上你的这份简历会受到大厂面试官的青睐。
多线程一直是一块难啃的骨头,但是又是非常重要的一块内容。说它难啃主要是因为多线程开发中,需要考虑的东西很多。
比如:何如在多线程并发写的情况下,保证你的数据一致性,不会别其他线程覆盖,很多人也会考虑到使用锁(分布式锁redisLock、单机版锁、乐观锁、cas)来保证数据的一致性。
但是,同时你也要考虑QPS,不要因为加了锁,后面导致系统的整体吞吐量大大下降,后面压测过不了,就有的你忙了。
所以说,掌握了多线程编程对于写代码提升是非常大的,考虑的东西也会比别人更多,而且可能还会出现一些奇奇怪怪的bug,排查起来非常困难,对于人的技术的考验是非常大的。
好了废话不多说,直接上干货。
线程池基本介绍
首先,来聊一聊啥是线程池,引用百度百科的解释:
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
用简单通俗的话来说,线程池就是类似于一种池子,里面养了很多的线程,再有任务的时候,可以直接拿取池子里面的线程来执行任务,用完之后,线程还给线程池,在下一次又有任务的时候同样这样执行。
之所以用“养”这个关键字,原因是他比正常的线程来说,线程池可以复用,在此使用的时候不用再次创建。
直接从线程池的功能角度来解释就是如此简单,我们还接触过其他的类似于这种的池化技术。比如:数据库连接池、HttpClient连接池、内存池、实例池等等。
我们可以发现,在这些所谓的池化思想,都存在很多共性:预先分配、循环使用、复用。
比如,连接池预先申请数据库连接,连接的复用,内存池预先分配内存,提高内存的分配效率,减少内存碎片等。
最后,还有很重要的关于作者方面就是要知道线程池的作者,他就是鼎鼎大名的:Doug Lea,并发编程大神,学Java的人都知道他(编程不识Doug Lea,写尽java也枉然)。
为什么使用线程池
那么什么使用线程池呢?其实理由答案都很简单,线程池相比传统的直接创建线程肯定是有优点。
我们学JVM的时候有聊到线程私有和线程共有的区域,至少单独的创建线程,就会单独给线程创建(虚拟机栈、程序计数器、本地方法栈),这些都是要占用内存的。
所以线程池的第一个优点就是:能够控制服务器资源,应该说合理的分配服务器资源,不至于过高的QPS,导致服务器资源分配完,从而导致整个服务器瘫痪。
另外的优点就是:线程复用,因为反复的创建和销毁线程对于性能的消耗也是有影响的,线程池反而能够降低线程创建和销毁资源的消耗。
最后一个就是优点也是我们最终的目的:就是为了优化系统,在大多数的情况下,线程池相比串行化的操作,异步的执行我们的任务,由原来的串行操作,修改成异步操作,降低了系统的响应时间。
所以,对于线程池最常用的一个操作和场景就是:把原来很多串行的查询操作,可以修改成异步,然后在服务层做聚合,返回给前端,提系统的响应时间(前提上下文之间没有数据的依赖)。
那么说了那么多的线程池的优点,线程池有啥缺点呢?缺点还是挺多的,比如:线程池的数量配置的不合理,会导致系统资源的耗尽、可能直接导致系统出现OOM异常。
另外的话使用线程池,也会带来多线程的问题,比如:数据的一致性、业务的复杂性、测试的复杂性(一般线程池的使用,都要结合测试,不断的进行压测,然后观察内存和CPU的变化影响怎么样)。
所以,我们在使用线程池的时候,必须要求用其利,避其害,精通线程的的底层原理,对于我们更加合理有效的使用线程池是非常有帮助的。
那么怎么才算是精通线程池呢?我个人认为主要掌握这几方面:
-
所使用的技术点(AQS、CAS、Reentrantlock)
-
常见的线程池的种类
-
设计模式(生产者-消费者模式、策略模式)
-
设计的思想(弹性、可伸缩)
-
执行的原理
-
源码的阅读
-
线程池的调优(线程数的设置)
是