Java多线程及线程池简介

本文主要有以下三部分内容:
第一部分:多线程有什么用?
第二部分:线程池有什么用?
第三部分:线程池相关好文章

多线程有什么用?

(1)发挥多核CPU的优势:笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
(2)防止阻塞:从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

线程池有什么用?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,
而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
举例说明:
假如一个服务器完成一项任务的时间为T:
T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程同步所需要的时间
T3 线程销毁的时间
显然 T= T1+T2+T3. 注意:这是一个理想化的情况。
可以看出,T1,T3是多线程自身带来的开销(在Java中,通过映射pThread,并进一步通过SystemCall实现native线程),我们渴望减少T1和T3的时间,从而减少T的时间。但是一些线程的使用者并没有注意到这一点,所以在线程中频繁的创建或者销毁线程,这导致T1和T3在T中占有相当比例。这显然突出的线程池的弱点(T1,T3),而不是有点(并发性)。所以线程池的技术正是如何关注缩短或调整T1,T3时间的技术,从而提高服务器程序的性能。
线程池技术关注的问题
1、通过对线程进行缓存,减少创建和销毁时间的损失
2、通过控制线程数量的阀值,减少线程过少带来的CPU闲置(比如长时间卡在I/O上了)与线程过多给JVM内存与线程切换时系统调用的压力。
其实线程池技术主要是减少线程创建、切换、销毁所占用时间,其中线程创建和销毁我们可以控制,线程切换主要是CPU来完成,我们能做到的就是减少线程创建和销毁带来的开销。

线程池相关好文章

Java并发编程:线程池的使用
本文是我见过对线程池讲解最好的文章,前两年看过一次保存了下来,昨天又看了一次感觉还是经典中的经典,Java并发编程:线程池的使用看完以后基本就掌握了线程池相关内容。
主要内容有:
一.Java中的ThreadPoolExecutor类
介绍相关参数、方法以及类之间的关系。
二.深入剖析线程池实现原理
这部分内容最重要,也是理解线程池的核心内容,主要内容有:
1.线程池状态:介绍线程池相关状态。
2.任务的执行:对任务提交给线程池之后到被执行的整个过程进行了讲解(理解线程池核心中的核心)。
3.线程池中的线程初始化
4.任务缓存队列及排队策略
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5.任务拒绝策略
1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6.线程池的关闭:shutdown()和shutdownNow()来关闭线程池。
7.线程池容量的动态调整:setCorePoolSize()和setMaximumPoolSize()可动态调整线程池容量大小。
三.使用示例
通过具体例子,展示线程池缓存、排队、拒绝策略,自己也可以写一些例子来验证线程池相关原理。
四.如何合理配置线程池的大小 
如何合理配置线程池大小提供一些参考。
以上是Java并发编程:线程池的使用主要内容介绍,也算是自己对文章内容的简单总结,建议直接阅读原文,并参考源码学习线程池。

参考文章:

Java并发编程:线程池的使用
OKHttp源码解析(三)--中阶之线程池和消息队列

推荐阅读更多精彩内容