【第二章:Java核心技术解析】第10节:Java进阶 - 高效并发编程(下)
大家好,很高兴我们可以继续学习交流Java高频面试题。本小节是Java进阶篇章中关于并发编程的最后一节。在前面两个小节中,我们介绍了一些多线程并发编程的基础高频考察知识点,本小节我们主要来介绍Java中的线程池相关知识点,另外还包括CountDownLatch,CyclicBarrier,ThreadLocal以及Atomic等关键字的解析。
(1)Java中的线程池有了解吗?
答:java.util.concurrent.ThreadPoolExecutor类就是一个线程池。客户端调用ThreadPoolExecutor.submit(Runnable task)提交任务,线程池内部维护的工作者线程的数量就是该线程池的线程池大小,有3种形态:
- 当前线程池大小:表示线程池中实际工作者线程的数量
- 最大线程池大小(maxinumPoolSize):表示线程池中允许存在的工作者线程的数量上限
- 核心线程大小(corePoolSize ):表示一个不大于最大线程池大小的工作者线程数量上限
线程池的优势体现如下:
- 线程池可以重复利用已创建的线程,一次创建可以执行多次任务,有效降低线程创建和销毁所造成的资源消耗;
- 线程池技术使得请求可以快速得到响应,节约了创建线程的时间;
- 线程的创建需要占用系统内存,消耗系统资源,使用线程池可以更好的管理线程,做到统一分配、调优和监控线程,提高系统的稳定性。
解析:
创建线程是有开销的,为了重复利用已创建的线程降低线程创建和销毁的消耗,提高资源的利用效率,所以出现了线程池。线程池的参数字段如下所示:
我们来看下JDK对各个字段的解释:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime :线程空闲但是保持不被回收的时间
- unit:时间单位
- workQueue:存储线程的队列
- threadFactory:创建线程的工厂
- handler:拒绝策略
线程池的排队策略:
当我们向线程池提交任务的时候,需要遵循一定的排队策略,具体策略如下:
- 如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队
- 如果运行的线程等于或者多于corePoolSize,则Executor始终首选将请求加入队列,而不是添加新线程
- 如果无法将请求加入队列,即队列已经满了,则创建新的线程,除非创建此线程超出maxinumPoolSize,在这种情况下,任务默认将被拒绝。
常见的线程池类型:
newCachedThreadPool( )
- 核心线程池大小为0,最大线程池大小不受限,来一个创建一个线程
- 适合用来执行大量耗时较短且提交频率较高的任务
newFixedThreadPool( )
- 固定大小的线程池
- 当线程池大小达到核心线程池大小,就不会增加也不会减小工作者线程的固定大小的线程池
newSingleThreadExecutor( )
- 便于实现单(多)生产者-消费者模式
常见的阻塞队列:
前面我们介绍了线程池内部有一个排队策略,任务可能需要在队列中进行排队等候。常见的阻塞队列包括如下的三种,接下来我们一起来看看吧。
ArrayBlockingQueue:
- 内部使用一个数组作为其存储空间,数组的存储空间是预先分配的
- 优点是 put 和 take操作不会增加GC的负担(因为空间是预先分配的)
- 缺点是 put 和 take操作使用同一个锁,可能导致锁争用,导致较多的上下文切换。
- ArrayBlockingQueue适合在生产者线程和消费者线程之间的并发程序较低的情况下使用。
LinkedBlockingQueue:
- 是一个无界队列(其实队列长度是Integer.MAX_VALUE)
- 内部存储空间是一个链表,并且链表节点所需的存储空间是动态分配的
- 优点是 put 和 take 操作使用两个显式锁(putLock和takeLock)
- 缺点是增加了GC的负担,因为空间是动态分配的。
- LinkedBlockingQueue适合在生产者线程和消费者线程之间的并发程序较高的情况下使用。
SynchronousQueue:
SynchronousQueue可以被看做一种特殊的有界队列。生产者线程生产一个产品之后,会等待消费者线程来取走这个产品,才会接着生产下一个产品,适合在生产者线程和消费者线程之间的处理能力相差不大的情况下使用。
我们前边介绍newCachedThreadPool时候说,这个线程池来一个线程就创建一个,这是因为其内部队列使用了SynchronousQueue,所以不存在排队。
关于线程池,你应该知道的事情:
- 使用JDK提供的快捷方式创建线程池,比如说newCachedThreadPool会出现一些内存溢出的问题,因为队列可以被塞入很多任务。所以,大多数情况下,我们都应该自定义线程池。
- 线程池提供了一些监控API,可以很方便的监控当前以及塞进队列的任务数以及当前线程池已经完成的任务数等。
考点分析:
线程池几乎是一个必考的知识点,所以我们必须熟练掌握线程池的基本参数及其含义,并且对排队策略有清晰的理解。常见线程池的类型,包括其使用到的阻塞队列等。
(2)CountDownLatch和CyclicBarrier有了解吗?
答: 两个关键字经常放在一起比较和考察,下边我们分别介绍。
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> Java开发岗高频面试题全解析,专刊正文共计31节,已经全部更新完毕。专刊分9个模块来对Java岗位面试中的知识点进行解析,包括通用面试技能,Java基础,Java进阶,网络协议,常见框架以及算法,设计模式等。专刊串点成面的解析每个面试题背后的技术原理,由浅入深,循序渐进,力争让大家掌握面试题目的背后的技术原理,摒弃背题模式的陋习。 专刊详细信息,请查阅专刊大纲和开篇词的介绍。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>