面试题19解析-线程池(下)

简介: 本文阅读大概需要15分钟。

在多线程开发中,经常需要多线程公用一个变量、对象或资源,我相信大多数开发者可以使用synchronized、volatile、ConcurrentHashMap等一些线程同步控制方式来开发多线程应用,Doug Lea大师的确为我们开发了很多Java并发控制工具,使得多线程的变得容易,但是如果我们仅仅只会用,而不明白其中原理,往往同步控制,写出的程序性能低下,遇到问题,并发程序难于调试,打击信心,进度拖延。因此,对于原理的理解,才是一个程序员能够立足于快速技术迭代的IT潮流下的资本,是真正的内功。扯远了,话题转回来,在讨论Java并发程序的共享问题之前,我们不得不先来看看Java的内存模型——JMM(Java Memory Module)。


一Java内存模型(JMM)


Java内存模型(JMM)是Java多线程共享变量的控制机制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。这里可能就有疑问了,既然是共享变量,一个线程对于共享变量的更改对于其他线程不应该是立即可见的吗?之所以有这样的疑问,是因为不了解JMM下多线程对于共享变量操作的规则。JMM共享变量操作结构图如下:


image.png


在JMM模型中,线程的运行都拥有各自线程的内存存储区域,抽象为线程的本地内存(Local Memory),注意这个本地内存概念是一个抽象概念,并不是真的就是计算机内存,他指的是线程运行状态下,存储变量的所有位置(计算机内存、缓存等)。通过上图可以看到,线程的共享变量是保存在主存中的,线程的本地内存在线程初始化过程中只是保存了共享变量的副本,并在运行过程中操作的是该副本变量,并不是主存中的变量。图中,线程1与线程2的本地内存都存储了共享变量B的副本,但它们却不是同一变量,也就是说两个线程对各自副本变量的操作对于另一线程来说是不可见的,而保证共享变量B对于两个线程的可见性的唯一方法,就是需要将变量副本与主存中的共享变量进行同步操作,而至于何时将副本刷入主存就是由JMM规则控制的。


二volatile线程可见性


在Java中关键字volatile控制了多线程变量可见性,当线程操作对volatile变量进行写操作时,JMM会将该线程的本地内存中的副本变量刷新到主存中去;当线程对volatile变量进行读操作时,线程先将本地内存共享变量设置为无效,线程就会从主存中读取共享变量的值了。JMM下volatile共享变量的操作就变成了下图模型:


image.png


从实质来说,共享变量的副本就是主存中变量的缓存,与软件开发系统中的缓存原理相同,在缓存失效时,缓存系统便会从持久存储中读取最新的变量值,当缓存值修改后,缓存系统会在合适时机将新值同步回持久存储中。JMM之所以在线程本地内存中使用副本,也是为了使线程对变量访问更快。从计算机体系的角度来看,线程的本地内存则很多时候是保存在CPU的缓存中的,CPU缓存速度远快于系统内存(因为CPU访问内存需要通过主板的总线系统,而CPU缓存就在CPU的内部,处理器可以直接访问,因此更快),所以说java中volatile的规则的是底层CPU缓存与内存之间进行数据同步的操作来实现的。


这里针对网络中有些对volatile内存语义解释为:“volatile变量会促使线程直接操作主存中的变量”的说法,这里进行辩证说明一下:本作者认为该说法不严密,在处理器与内存之间没有任何缓存介质时,该命题成立。如果处理器直接操作内存,需要获取BUS总线锁,在BUS总线锁被占用的过程中,其他处理器无法对内存进行任何操作,是一种低效的内存控制技术,随着处理器的发展,CPU增加了缓存系统,只有CPU缓存失效或缓存未命中时,处理器才会从内存读取数据,不同的处理器都担保了处理器缓存与内存变量之间的同步的机制,因此这里只可以认为是“等效”于直接操作主存变量,并不一定是直接操作。


对于volatile例子:我们经常通过主线程与子线程之间通过一个布尔变量isRunning来控制子线程退出的时机,在JVM -server运行模式下,如果isRunning没有声明为volatile变量,子线程会一直在私有堆栈中读取isRunning变量,从而导致子线程一直无法退出。在《Effective JAVA》中,该现象被称为“活性失败”。这个例子,也说明了volatile使用的重要性,有些程序员会错误认为,isRunning变量迟早会被同步,的确,两个变量迟早会被同步,认为没有必要用volatile修饰,但是却错误估计了该情况下同步需要的时间有可能与线程的生命周期相同。


有些Java初学者,一直分不清可见性与锁的区别。锁控制的是代码块的同步,控制的是指令的执行顺序,而可见性指的是处理器确保共享变量在不同线程之间的可见,可见性不能保证共享变量操作的原子性,两者解决的问题不同,面对的对象也不同。例如共享volatile变量i++操作,volatile可以确保变量i的值多线程可见,但却不能保证i++是原子操作,因为i++实际上也是由三个原子操作组成:read i、inc、 write i,在多线程环境下,依然可能产生对变量i的脏读,脏写现象。因此只有读者明白JMM可见性产生的原因,JMM共享变量的结构,才能理解并发编程中可见性的作用。


那么对于变量的原子操作是不是必须加锁?不一定,这里建议读者参看AtomicInteger类的实现,其使用无锁CAS操作确保了volatile变量的原子操作。


三为什么CPU需要缓存


CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。缓存大小是CPU的重要指标之一,而且缓存的结构和大小对CPU速度的影响非常大,CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。但是从CPU芯片面积和成本的因素来考虑,缓存都很小。


image.png


按照数据读取顺序和与CPU结合的紧密程度,CPU缓存又可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。


既然缓存是好动西,那为什么不把数据都放入缓存?造价问题,CPU缓存造价远高于内存,这和内存造价远高于硬盘一个道理。

相关文章
|
1月前
|
存储 缓存 NoSQL
Redis常见面试题全解析
Redis面试高频考点全解析:从过期删除、内存淘汰策略,到缓存雪崩、击穿、穿透及BigKey问题,深入原理与实战解决方案,助你轻松应对技术挑战,提升系统性能与稳定性。(238字)
|
3月前
|
存储 安全 测试技术
Python面试题精选及解析
本文详解Python面试中的六大道经典问题,涵盖列表与元组区别、深浅拷贝、`__new__`与`__init__`、GIL影响、协程原理及可变与不可变类型,助你提升逻辑思维与问题解决能力,全面备战Python技术面试。
155 0
|
4月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
201 0
|
5月前
|
Web App开发 缓存 前端开发
浏览器常见面试题目及详细答案解析
本文围绕浏览器常见面试题及答案展开,深入解析浏览器组成、内核、渲染机制与缓存等核心知识点。内容涵盖浏览器的主要组成部分(如用户界面、呈现引擎、JavaScript解释器等)、主流浏览器内核及其特点、从输入URL到页面呈现的全过程,以及CSS加载对渲染的影响等。结合实际应用场景,帮助读者全面掌握浏览器工作原理,为前端开发和面试提供扎实的知识储备。
251 4
|
1月前
|
监控 Java 关系型数据库
面试性能测试总被刷?学员真实遇到的高频问题全解析!
面试常被性能测试题难住?其实考的不是工具,而是分析思维。从脚本编写到瓶颈定位,企业更看重系统理解与实战能力。本文拆解高频面试题,揭示背后考察逻辑,并通过真实项目训练,帮你构建性能测试完整知识体系,实现从“会操作”到“能解决问题”的跨越。
|
5月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
3124 48
|
5月前
|
前端开发 JavaScript 开发者
2025 最新 100 道 CSS 面试题及答案解析续篇
本文整理了100道CSS面试题及其答案,涵盖CSS基础与进阶知识。内容包括CSS引入方式、盒模型、选择器优先级等核心知识点,并通过按钮、卡片、导航栏等组件封装实例,讲解单一职责原则、样式隔离、响应式设计等最佳实践。适合前端开发者巩固基础、备战面试或提升组件化开发能力。资源地址:[点击下载](https://pan.quark.cn/s/50438c9ee7c0)。
131 5
2025 最新 100 道 CSS 面试题及答案解析续篇
|
5月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
332 6
|
5月前
|
NoSQL Java 微服务
2025 年最新 Java 面试从基础到微服务实战指南全解析
《Java面试实战指南:高并发与微服务架构解析》 本文针对Java开发者提供2025版面试技术要点,涵盖高并发电商系统设计、微服务架构实现及性能优化方案。核心内容包括:1)基于Spring Cloud和云原生技术的系统架构设计;2)JWT认证、Seata分布式事务等核心模块代码实现;3)数据库查询优化与高并发处理方案,响应时间从500ms优化至80ms;4)微服务调用可靠性保障方案。文章通过实战案例展现Java最新技术栈(Java 17/Spring Boot 3.2)的应用.
459 9

热门文章

最新文章

推荐镜像

更多
  • DNS