1.Volatile的分析分析实现原理(看这篇就够了)
2.源码分析: Java中锁的种类与特性详解
3.面试说两天给结果给我,那都没有机会为什么不说今天给结果给我?
4.编程「锁」事|详解乐观锁 CAS 的源码源码技术原理
5.ReentrantLock 源码解析 | 京东云技术团队
6.debugåreleaseçåºå«
Volatile的实现原理(看这篇就够了)
探讨并发编程的核心要素——原子性、可见性与有序性,分析分析Volatile作为关键角色,源码源码在多线程环境中确保了可见性与有序性,分析分析成为轻量级同步机制的源码源码进化计算源码代表。本文旨在全面剖析Volatile的分析分析实现原理,通过理论与实践相结合的源码源码方式,帮助读者深入理解并熟练掌握Volatile变量的分析分析正确运用。
Volatile关键字与Java内存模型
在深入探讨Volatile前,源码源码首先回顾Java内存模型的分析分析三要素——原子性、可见性与有序性,源码源码这是分析分析并发编程的基石。
1. 原子性
原子性指的源码源码是不可分割的操作,确保操作要么全成功要么全失败。分析分析例如,简单的读取与赋值操作是原子的,而复杂的操作如自增、加法等则不是。
2. 可见性
当一个线程修改共享变量时,其他线程能够立即访问到修改后的值。
3. 有序性
编译器与处理器为优化性能可能改变指令顺序,但这种重排序不影响单线程执行,却可能干扰多线程执行的正确性。
Volatile的作用与限制
Volatile作为类型修饰符,为共享变量赋予了两层语义:确保多线程下的可见性与禁止指令重排序。然而,它只能保证单次读写操作的原子性,对于复杂操作如自增等不适用。
Java内存模型详解
Java内存模型(JMM)提供了一组规则,定义了变量在主内存与工作内存中的访问方式,以实现跨平台的一致性。
1. 变量存储在主内存
每个线程拥有自己的工作内存,用于存储变量的副本,线程间变量传递需通过主内存同步。
2. 独立的工作内存
每个线程独立,其工作内存中变量的副本仅线程可见,不与其他线程共享。
Volatile的实现原理
Volatile通过特定的内存模型操作确保可见性与有序性,其原理基于Java内存模型的规则,通过lock、unlock、read、load等操作实现。
1. lock
锁定共享变量,确保线程独占。
2. unlock
解除锁定,其他线程有机会访问。
3. read
从主内存读取变量值。
4. load
将读取值存储到工作内存。
5. use
将值传递给代码执行引擎。
6. assign
将处理结果回写到工作内存。
7. store
将工作内存更新同步至主内存。
8. write
最终写入共享变量。
指令规则
Volatile通过上述操作确保多线程环境中的可见性与有序性,实现内存模型的规则。
源码案例
本文介绍了Volatile的实现原理,包括理论知识与源码实例,帮助读者理解其在并发编程中的应用。欲了解更多内容,欢迎访问作者主页获取视频详解与技术连载。
---END---
源码分析: Java中锁的种类与特性详解
在Java中存在多种锁,包括ReentrantLock、Synchronized等,它们根据特性与使用场景可划分为多种类型,如乐观锁与悲观锁、可重入锁与不可重入锁等。本文将结合源码深入分析这些锁的设计思想与应用场景。
锁存在的意义在于保护资源,防止多线程访问同步资源时出现预期之外的错误。举例来说,当张三操作同一张银行卡进行转账,如果银行不锁定账户余额,可能会导致两笔转账同时成功,扫矿源码违背用户意图。因此,在多线程环境下,锁机制是必要的。
乐观锁认为访问资源时不会立即加锁,仅在获取失败时重试,通常适用于竞争频率不高的场景。乐观锁可能影响系统性能,故在竞争激烈的场景下不建议使用。Java中的乐观锁实现方式多基于CAS(比较并交换)操作,如AQS的锁、ReentrantLock、CountDownLatch、Semaphore等。CAS类实现不能完全保证线程安全,使用时需注意版本号管理等潜在问题。
悲观锁则始终在访问同步资源前加锁,确保无其他线程干预。ReentrantLock、Synchronized等都是典型的悲观锁实现。
自旋锁与自适应自旋锁是另一种锁机制。自旋锁在获取锁失败时采用循环等待策略,避免阻塞线程。自适应自旋锁则根据前一次自旋结果动态调整等待时间,提高效率。
无锁、偏向锁、轻量级锁与重量级锁是Synchronized的锁状态,从无锁到重量级锁,锁的竞争程度与性能逐渐增加。Java对象头包含了Mark Word与Klass Pointer,Mark Word存储对象状态信息,而Klass Pointer指向类元数据。
Monitor是实现线程同步的关键,与底层操作系统的Mutex Lock相互依赖。Synchronized通过Monitor实现,其效率在JDK 6前较低,但JDK 6引入了偏向锁与轻量级锁优化性能。
公平锁与非公平锁决定了锁的分配顺序。公平锁遵循申请顺序,非公平锁则允许插队,提高锁获取效率。
可重入锁允许线程在获取锁的同一节点多次获取锁,而不可重入锁不允许。共享锁与独占锁是另一种锁分类,前者允许多个线程共享资源,后者则确保资源的独占性。
本文通过源码分析,详细介绍了Java锁的种类与特性,以及它们在不同场景下的应用。了解这些机制对于多线程编程至关重要。此外,还有多种机制如volatile关键字、原子类以及线程安全的集合类等,需要根据具体场景逐步掌握。
面试说两天给结果给我,那都没有机会为什么不说今天给结果给我?
今天给大家分享一个关于一次奇葩面试:喊价K,HR却给了K的经历,网友评论说:面试造飞机,工作拧螺丝?自报家门
先做个自我介绍,楼主坐标帝都,5 年经验,跳槽之前在一家传统小公司,年薪 万。
这次面试前前后后大概两个月的时间,面试了大概 6 家公司,命中 4 家,最终去了一家估值 亿美金的生鲜电商独角兽,年薪 万,刚好翻倍。
面试过程
话不多说,直接进入面试现场!
好未来
开始面试第一天上午投递好未来,下午 3 点面试,wordpress 网站源码一共面试了 3 轮,问的问题比较多。
第一轮
面试官看了我的简历,首先让我画出 Eureka 的执行流程,这块在之前的准备过程中有深入看过,因此比较流畅的画出来并配合解释说明。
之后问到项目中使用分布式锁解决缓存重建并发的问题,并要求画出实际的执行流程,数据库也问的比较多,像事务的隔离级别,MySQL 实现可重复读的原理,索引等。
面试官给出了一个场景,在数据库主从同步的情况下,如果从库同步主库的数据延迟比较高,怎么才能在写到主库后立刻能够读取到数据。
我解释了主从同步的原理,并以此说明主库到从库的复制一定是有延迟的,因此要保证当写到主库的时候立刻能读到数据。
要么就直接配置那个接口读数据的话直接走主库,因为这种写完主库立刻要读取数据的场景比较少,可以做些特殊配置。
另一种方案就是在往主库写数据的时候,可以直接往内存缓存中写一份,设置一个较短的过期时间,后面可以直接从缓存中读到数据。我说完之后,面试官也没给出评价,就这么过去了。
此外,还问到一些基础性的问题,比较印象深刻的是:在加锁的时候,用什么锁对象是内存占用最小的,我说是 Object 对象,面试官说不对,我一时没想出来,面试结束后和朋友探讨,觉得应该是长度为 0 的 byte 数组。
其他还问到了 Collections.sort() 使用的排序算法,AQS,线程池,ThreadLocal 等等问题,主要都是一些考察基本功的问题,一轮面试就这么过去了!
第二轮
面试官更关注对一些技术的理解,问到了 ElasticSearch 的一些基础以及它和 MySQL 的区别在哪里;Eureka 和 Zookeeper 做服务发现的区别在哪里。
还问了分布式限流有哪些方案,以及用线程池进行限流的缺陷是什么,项目中系统日志的处理;还有 JVM 模型,JMM 模型,垃圾回收机制,垃圾收集器等问题。
之后聊了一些设计模式的使用,在项目中使用了哪些设计模式,对设计模式的几个原则的理解。
第二轮结束后,由于第三轮的面试官在开会,所以等了一段时间,等面试官来了之后,只聊了很短时间,面试官就说还有别的事,今天先到这里了。
主要问到了上家公司的加班情况,对加班的认识,职业规划,也问了几个技术问题,像 Tomcat 的优化这块,自我感觉答的不是很好。
整个面试从 3 点到 7 点,有点虎头蛇尾的感觉,结束后也没有消息了。
到家
面试一共三轮,上午 点过去,两轮技术面,外卖公司源码下午两点过去,等了一会,然后跟 HR 聊了有半个多小时,HR 说明在一周之内会有结果。
第一轮
第一轮面试官的问题主要集中在基础上,我大概罗列了问到的一些问题,不同的简历不同人肯定问的也不太一样,有兴趣的同学可以参考看看。
主要是 JVM 模型,锁的原理,Synchronized 和 ReentrantLock的区别,偏向锁/轻量级锁/重量级锁的原理,能否从偏向锁直接升级成重量级锁。
Java 并发包里有哪些类,如何使用,线程池原理和参数配置,JVM 调优,堆大小的设置,多线程的线程数的设置,Volatile 原理,ThreadLocal 原理和使用。
Redis 和 Zookeeper 如何实现分布式锁,Redis 的数据类型,一些具体命令,比如要获取一个有序列表的前 个元素应该用什么命令。
数据库索引的使用,聚簇索引和非聚簇索引,没有主键的话,数据如何组织。
B+ 树的原理,InnoDB 引擎和 MyISAM 引擎的区别和使用场景,数据库隔离级别和原理,MySQL 的分库分表,MQ 的可靠性和顺序性,ES 插入数据的原理等。
第二轮
第二轮是部门 Leader 来面试,这轮面试主要集中在框架源码上,我画出了源码的执行流程,之后面试官在一些点深问,因为这块我看的比较全面,问的问题基本都答出来了。
然后这里面试官还问了在源码中我有学到什么东西,我讲了使用配置类代替 Properties 文件,Volatile 在单例模式中的使用,内存的多级缓存机制,线程池的各种不同应用场景,MeasureRate 统计一分钟内心跳次数,批处理机制等。
这里我的回答主要集中在代码编写层面,也可以从架构层面说下学到了哪些,我觉得后者更有高度。
最后我向面试官咨询了这个岗位具体做的事情,部门是基础服务部,面试官画图给我说明了部门内部一些项目划分,技术栈的使用,后续的规划等内容,并约我下午继续跟 HR 聊。
HR 面
下午跟 HR 的面试,HR 顺着简历上的公司一个个聊,问了离职原因,公司情况,如何向上司提出离职的,团队规模,是否带团队。
还问了上午面试的岗位知不知道具体要做什么,之后 HR 说了下公司的一些情况,上班时间,福利,加班情况,问了我现在的薪资情况,期望薪资,我问了下出结果的时间,HR 说一周之内。源码交付函
第二周的周五下午六七点的时候,这家公司 HR 给我打电话,告诉我面试通过了,之后提到了给我的薪资,算下来竟然只给了我一个 5% 的涨幅。
HR 给出的解释是,因为我前家公司上一年只发了 薪,而他们有 薪和两个多月的绩效,用 个月的薪水除以 ,算下来平均到每个月也能达到我期望薪资的水平。
这个计算方法实在是膈应人,虽然 HR 后来表示可以跟 CEO 申请提高每月的 Base(大概提高到 % 的水平吧),不过当时我已经有较为满意的 Offer 了,还是决定不去这家了。
某生鲜电商独角兽
由于前面说了薪资,就不说具体公司名字了。这家公司我面试了两天,一共三面,第一天笔试加初面,然后第二天有两轮复试。
第一轮
一面主要还是基础,集中在 IO/并发/缓存/Redis/Zookeeper/分布式/JVM/数据库等。
其中问到 Redis 的单线程模型的时候,我这块了解的不是很清楚,只是知道使用 NIO 的方式,然后以自己的理解去说了,面试官表示这可能是我看过别的框架的模式,跟 Redis 搞混了,不过也算是答上来一些了。
之后聊了一些项目的情况,比如每日的访问量有多少,QPS 多少,订单量多少等数据,据此得出数据库的访问压力如何。
另外也深入问了使用分布式事务的一些问题,还有分布式事务在时间上的性能。
所以这里给各位兄弟强调一下,对自己的项目一定要非常熟悉,各个点都要考虑到。
一面跟面试官聊的还挺好,面试官也表示我的基础还不错,问我是不是平时都有学习,之后就是约二面了。
由于当时已经下午 1 点了,后面的面试官也在中午休息,而我下午也还有别的面试,因此 HR 跟我约第二天来复试。
第二轮
二面的面试官也聊了基础和一些设计上的问题,比如同时访问三个有相同功能的 API,要求将执行最快的结果返回,有哪些方式,这块主要还是考察对并发编程,并发控制的理解和掌握,有一些并发控制的类能够做到。
其他的还问到了,要开发一个新的 API,需要考虑哪些方面,把所有要考虑的地方都说出来,大家可以说下边界处理,高可用,并发问题,可扩展性,幂等性,重试机制等等,可以说的非常多。
总体问了有 6 块内容吧,面试官一边问也一边在记录,一些基础的问题这里就不再多说了。
第三轮
三面的面试官问的要更底层一些,Java 线程与内核线程的关系,与进程的关系;关于并发我所了解的方方面面。
对于这个,我从为什么有并发,并发问题产生的根源,解决并发问题的一些理论,Java 中解决并发问题的方式,不同方式的适用场景和对比等方面进行了回答。
另外还问到 Redis 的几种数据类型,以及每种数据类型的底层实现,跳表这种数据结构如何插入数据, Hash 如何扩容。
这块我跟面试官说具体扩容规则不太了解,然后向面试官说了我了解的 Java 中的 HashMap 的扩容规则和具体实现。
Tips:面试时如果遇到自己不太熟悉的部分,可以稍作变通,把自己熟悉的内容和面试官的问题结合起来。
之后又问了一些小的知识点,有的也没答好,像 CopyOnWrite 就不知道用来做什么,然后就是一些为什么离职之类的问题,对未来职业发展的考虑等。
之后面试官问我有什么想了解的,也问了我的期望薪资,我说了具体的数,也表示没想要太多,更看重平台的发展,最后面试官说明天 HR 会打电话给我。
HR 面
最后就是跟 HR 的沟通了,第二天 HR 打来电话告知面试通过,然后问了我期望薪资,沟通入职时间,之后加微信,按照 HR 的要求提供了一些材料,第二天就收到 Offer 了。
PS:最终楼主选择了这家公司,除了很有竞争力的薪资之外,我还很看重这家公司的发展平台,因为他们有非常大的用户量,会遇到各种技术挑战,是很好的提升锻炼的机会。
然后这里有一个开篇提到的小插曲:当时 HR 电话问我期望薪资的时候,我说 K。
结果后续加微信聊天时,HR 告诉我技术面试的反馈很好,决定给我 K,一个月还有 的补助,算下来一个月有 K,发 个月。这种 HR 主动加薪的事情我还是第一次见,意外之喜,哈哈!
玩吧
这家公司的职位是去做 App 后台的,用户量也不错,面试一共两轮技术面,最后是 HR 面。
第一轮
一面的时候,网络这块问的比较多,三次握手,四次挥手什么的,还有整个网络请求的执行流程,数据包的大小,对长连接的理解等。
然后数据库这块也问了一些,提供了一个场景,假如要实现一个最简单的朋友圈,用户可以看到朋友的朋友圈动态,朋友也可以看到用户发的动态,然后问表的设计。
我说了自己的实现,像用户表,好友表。面试官问有没有更好的方式,我没答上来,面试官表示这个轻易可能想不到,就问别的问题了,别的也没什么特殊的问题,都是一些基础的东西,大概聊了一个小时吧,就到了第二面了。
第二轮
二面是技术总监面的,整体没怎么聊技术,就是一些个人素质上的考察。比如:
为什么会选择做开发,没做别的用三个短语来描述自己的优点说说自己的缺点现在公司有系统稳定运行着,如果你发现了有新的技术能够改善现有系统,你会不会引进,会考虑哪些方面日常学习的方式,看过哪些书有没有带团队,描述下团队成员的优缺点,有没有改善有没有面试过别人,会从哪些方面考察职业规划是怎样的,想做技术管理还是技术专家对 Shell 熟不熟悉,写个 Word-Count 用到哪些命令最后还聊了下公司的氛围,项目的情况等。然后也没啥特殊的,就过了。
HR 面
最后跟 HR 聊,主要还是说了下公司的福利待遇,公司的氛围,也问了我现在有没有 Offer,对他们的感觉怎么样。
然后问了之前公司的薪资和现在的期望薪资,最后加了微信,告诉我两天内给结果。最后也是成功通过了面试并拿到了 Offer。
友信金服-人人贷
这家公司面试有三轮,大同小异,这里简短的说一下。
第一轮
一面仍然是基础的考察,像 CAS 的理解,和它存在的问题,ConcurrentHashMap 的锁机制,ElasticSearch 倒排索引,Eureka 的底层源码,还有服务访问的重试机制等等。
第二轮
二面上来问了垃圾回收的问题,类似下面的代码:
问 a 和 b 能否被垃圾回收?这里主要考察 JVM 如何判断一个对象是否可以被回收,是通过引用计数还是可达性分析,引用计数的方式会产生像上面代码一样的循环引用的问题,所以 JVM 没有采用这种方式。
第二个问题是,如果有个跟 Java 中原生的 String 一模一样的类,包括包名,类名都是一样的,方法也是一样的,唯独比原生的 String 的方法多个打印输出语句。
然后把它放进项目的依赖中,在写程序的时候,导入 String 类,问到底执行的是 Java 原生的 String 的方法还是自己写的 String 方法。
对于这个问题,可以考虑下 Java 中类加载的双亲委派模型。
然后就聊了项目的一些架构,问的比较细,要求我对每块都详细画图解释。
最后就是让画一个 Spring Cloud 技术栈所有框架的整体执行流程图,并对 Hystrix 的限流熔断机制做了解释说明,别的好像也没什么了。
这之后二面算是结束了,面试官和我说了下自己团队的情况,人员情况,要做的项目的情况等。
第三轮
最后一面是业务总监面的,面试官让我说了下自己在公司做了哪些事情,我挑其中一个项目做了仔细说明,然后说了下职业规划,对行业的看法等等。
最后 HR 和我加了微信,同样说是两天内给结果,不过第二天他们就给出通过的结果了,然后发了 Offer。
某实时数据分析服务公司
这是一家做体育赛事的实时数据分析展示的公司,公司不大,去年拿了 A 轮融资,看网上整体评价还不错,就去试了试。
面试总共有技术两轮,HR 一轮。去的时候首先是写笔试题。做完之后进入面试。
第一轮面试官没有聊太久,问的问题也比较偏基础,就是一些面试常问的问题,然后说了 Eureka 的执行原理,说完之后,面试官就去叫技术总监了。
第二轮面试是技术总监面的,技术点没问太多,主要集中在之前的笔试题上,笔试题包括 SQL 的考察,还有几道算法题:找出有序数组中指定元素出现的次数;二叉查找树从小到大排序。因为时间的问题,我主要写了实现思路。
还有一题是,有 瓶水,其中一瓶有毒,小白鼠喝一滴有毒的水一小时后会死,要在一小时找出来哪瓶水有毒最少需要几只小白鼠。
在 SQL 的考察这块,面试官看完我的答案后,又改了其中的需求,要求给出 SQL 的实现,另外也问到了 SQL 的执行效率。
这里给大家强调一下,我面的基本上每家公司面试都会问到数据库,所以这块还是挺重要的,需要重点去看。
然后关于找出有序数组中指定元素出现次数的问题,原来要求的时间复杂度是 O(lgn),后来面试官说不要求任何时间空间复杂度,如何简单的实现,我给出的方案是用 HashMap,相同的 Key 每出现一次,Value 加 1。
然后是小白鼠问题,说了解题思路,主要就是用位的思想,对 瓶水编码,实际只需要 4 个位就可以。
之后面试官还现场出了别的算法题,我基本都给出了结果,总体而言面试还比较顺畅,之后聊了下职业规划,技术发展,学习新技术的方法,面试官也聊了之后他们准备做的事情,并给我现场演示了他们的项目。
最后到了 HR 面,主要聊了下上家公司离职的原因,公司福利,上下班时间,我的期望薪水,还问到之前有没有带团队的经历等。
最终他们在第二周的周四才给出面试通过的结果并表示正在走 Offer 流程,由于 CEO 不在,在薪资上还没最终确定,我因为有了更满意的 Offer,因此婉拒了。
总结
总结一下,这两个月的面试,我觉得最重要的就是基础和项目这两块,基础一定要扎实,否则第一轮面试可能都过不了。
JVM,并发是非常高频被问到的地方,在开始面试之前一定要好好准备,另外也需要有自己非常熟悉的领域。
在这个领域里,面试官的一切问题你都可以 Hold 住,我觉得,对于这种基础好,而且有自己长处的面试者,面试官没有理由不喜欢。
还有项目这块,对项目的细节一定要清楚,各种方案的设计思路,实现细节等等都要了如指掌,这样在面试官对各种细节的追问下不至于手忙脚乱。
编程「锁」事|详解乐观锁 CAS 的技术原理
本文深入探讨乐观锁的核心实现方式——CAS(Compare And Swap)技术原理。CAS是一种在多线程环境下实现同步功能的机制,相较于悲观锁的加锁操作,CAS允许在不使用锁的情况下实现多线程间的变量同步。Java的并发包中的原子类正是利用CAS实现乐观锁。
CAS操作包含三个操作数:需要更新的内存值V、进行比较的预期数值A和要写入的值B。其逻辑是将内存值V与预期值A进行比较,当且仅当V值等于A时,通过原子方式用新值B更新V值(“比较+更新”整体是一个原子操作),否则不执行任何操作。一般情况下,更新操作会不断重试直至成功。
以Java.util.concurrent.atomic并发包下的AtomicInteger原子整型类为例,分析其CAS底层实现机制。方法`atomicData.incrementAndGet()`内部通过Unsafe类实现。Unsafe类是底层硬件CPU指令复制工具类,关键在于compareAndSet()方法的返回结果。
`unsafe.compareAndSwapInt(this, valueOffset, expect, update)`
此方法中,参数`this`是Unsafe对象本身,用于获取value的内存偏移地址。`valueOffset`是value变量的内存偏移地址,`expect`是期望更新的值,`update`是要更新的最新值。如果原子变量中的value值等于`expect`,则使用`update`值更新该值并返回true,否则返回false。
至于`valueOffset`的来源,这里提到value实际上是volatile关键字修饰的变量,以保证在多线程环境下的内存可见性。
CAS的底层是Unsafe类。如何通过`Unsafe.getUnsafe()`方法获得Unsafe类的实例?这是因为AtomicInteger类在rt.jar包下,因此通过Bootstrap根类加载器加载。Unsafe类的具体实现可以在hotspot源码中找到,而unsafe.cpp中的C++代码不在本文详细分析范围内。对CAS实现感兴趣的读者可以自行查阅。
CAS底层的Unsafe类在多处理器上运行时,为cmpxchg指令添加lock前缀(lock cmpxchg),在单处理器上则无需此步骤(单处理器自身维护单处理器内的顺序一致性)。这一机制确保了CAS操作的原子性。
最后,同学们会发现CAS的操作与原子性密切相关。CPU如何实现原子性操作是一个深入的话题,有机会可以继续探索。欢迎在评论区讨论,避免出现BUG!点赞转发不脱发!
ReentrantLock 源码解析 | 京东云技术团队
并发指同一时间内进行了多个线程。并发问题是多个线程对同一资源进行操作时产生的问题。通过加锁可以解决并发问题,ReentrantLock 是锁的一种。
1 ReentrantLock
1.1 定义
ReentrantLock 是 Lock 接口的实现类,可以手动的对某一段进行加锁。ReentrantLock 可重入锁,具有可重入性,并且支持可中断锁。其内部对锁的控制有两种实现,一种为公平锁,另一种为非公平锁.
1.2 实现原理
ReentrantLock 的实现原理为 volatile+CAS。想要说明 volatile 和 CAS 首先要说明 JMM。
1.2.1 JMM
JMM (java 内存模型 Java Memory Model 简称 JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量的访问方式.
由于 JMM 运行的程序的实体是线程。而每个线程创建时 JMM 都会为其创建一个自己的工作内存 (栈空间), 工作内存是每个线程的私有数据区域。而 java 内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作 (读取赋值等) 必须在自己的工作内存中去进行,首先要将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成。
如图所示:线程 A 对变量 A 的操作,只能是从主内存中拷贝到线程中,再写回到主内存中。
1.2.2 volatile
volatile 是 JAVA 的关键字用于修饰变量,是 java 虚拟机的轻量同步机制,volatile 不能保证原子性。 作用:
作用:CAS 会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读 - 改 - 写操作。
1.2.4 AQSAQS 的全称是 AbstractQueuedSynchronizer(抽象的队列式的同步器),AQS 定义了一套多线程访问共享资源的同步器框架。
AQS 主要包含两部分内容:共享资源和等待队列。AQS 底层已经对这两部分内容提供了很多方法。
2 源码解析
ReentrantLock 在包 java.util.concurrent.locks 下,实现 Lock 接口。
2.1 lock 方法
lock 分为公平锁和非公平锁。
公平锁:
非公平锁:上来先尝试将 state 从 0 修改为 1,如果成功,代表获取锁资源。如果没有成功,调用 acquire。state 是 AQS 中的一个由 volatile 修饰的 int 类型变量,多个线程会通过 CAS 的方式修改 state,在并发情况下,只会有一个线程成功的修改 state。
2.2 acquire 方法
acquire 是一个业务方法,里面并没有实际的业务处理,都是在调用其他方法。
2.3 tryAcquire 方法
tryAcquire 分为公平和非公平两种。
公平:
非公平:
2.4 addWaiter 方法
在获取锁资源失败后,需要将当前线程封装为 Node 对象,并且插入到 AQS 队列的末尾。
2.5 acquireQueued 方法
2.6 unlock 方法
释放锁资源,将 state 减 1, 如果 state 减为 0 了,唤醒在队列中排队的 Node。
3 使用实例
3.1 公平锁
1. 代码:
2. 执行结果:
3. 小结:
公平锁可以保证每个线程获取锁的机会是相等的。
3.2 非公平锁
1. 代码:
2. 执行结果:
3. 小结:
非公平锁每个线程获取锁的机会是随机的。
3.3 忽略重复操作
1. 代码:
2. 执行结果:
3. 小结:
当线程持有锁时,不会重复执行,可以用来防止定时任务重复执行或者页面事件多次触发时不会重复触发。
3.4 超时不执行
1. 代码:
2. 执行结果:
3. 小结:
超时不执行可以防止由于资源处理不当长时间占用资源产生的死锁问题。
4 总结
并发是现在软件系统不可避免的问题,ReentrantLock 是可重入的独占锁,比起 synchronized 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。
debugåreleaseçåºå«
Debugé常称为è°è¯çæ¬ï¼å®å å«è°è¯ä¿¡æ¯ï¼å¹¶ä¸ä¸ä½ä»»ä½ä¼åï¼ä¾¿äºç¨åºåè°è¯ç¨åºãRelease称为åå¸çæ¬ï¼å®å¾å¾æ¯è¿è¡äºåç§ä¼åï¼ä½¿å¾ç¨åºå¨ä»£ç 大å°åè¿è¡é度ä¸é½æ¯æä¼çï¼ä»¥ä¾¿ç¨æ·å¾å¥½å°ä½¿ç¨ã
Debug å Release ççæ£ç§å¯ï¼å¨äºä¸ç»ç¼è¯é项ãä¸é¢ååºäºåå«é对äºè çé项ï¼å½ç¶é¤æ¤ä¹å¤è¿æå ¶ä»ä¸äºï¼å¦/Fd /Foï¼ä½åºå«å¹¶ä¸éè¦ï¼é常ä»ä»¬ä¹ä¸ä¼å¼èµ· Release çé误ï¼å¨æ¤ä¸è®¨è®ºï¼
Debug çæ¬
åæ° å«ä¹
/MDd /MLd æ /MTd ä½¿ç¨ Debug runtime library (è°è¯çæ¬çè¿è¡æ¶å»å½æ°åº)
/Od å ³éä¼åå¼å ³
/D "_DEBUG" ç¸å½äº #define _DEBUG,æå¼ç¼è¯è°è¯ä»£ç å¼å ³ (主è¦é对assertå½æ°)
/ZI å建 Edit and continue(ç¼è¾ç»§ç»)æ°æ®åºï¼è¿æ ·å¨è°è¯è¿ç¨ä¸å¦æä¿®æ¹äºæºä»£ç ä¸ééæ°ç¼è¯
/GZ å¯ä»¥å¸®å©æè·å åé误
/Gm æå¼æå°åéé¾æ¥å¼å ³ï¼ åå°é¾æ¥æ¶é´
Release çæ¬
åæ° å«ä¹
/MD /ML æ /MT 使ç¨åå¸çæ¬çè¿è¡æ¶å»å½æ°åº
/O1 æ /O2 ä¼åå¼å ³ï¼ä½¿ç¨åºæå°ææå¿«
/D "NDEBUG" å ³éæ¡ä»¶ç¼è¯è°è¯ä»£ç å¼å ³ (å³ä¸ç¼è¯assertå½æ°)
/GF å并éå¤çåç¬¦ä¸²ï¼ å¹¶å°å符串常éæ¾å°åªè¯»å åï¼ é²æ¢è¢«ä¿®æ¹
å®é ä¸ï¼Debug å Release 并没ææ¬è´¨ççéï¼ä»ä»¬åªæ¯ä¸ç»ç¼è¯é项çéåï¼ç¼è¯å¨åªæ¯æç §é¢å®çé项è¡å¨ãäºå®ä¸ï¼æ们çè³å¯ä»¥ä¿®æ¹è¿äºé项ï¼ä»èå¾å°ä¼åè¿çè°è¯çæ¬ææ¯å¸¦è·è¸ªè¯å¥çåå¸çæ¬ã
åªäºæ åµä¸ Release çä¼åºé
æäºä¸é¢çä»ç»ï¼æ们åæ¥éä¸ªå¯¹ç §è¿äºé项çç Release çé误æ¯ææ ·äº§çç
1ãRuntime Libraryï¼é¾æ¥åªç§è¿è¡æ¶å»å½æ°åºé常åªå¯¹ç¨åºçæ§è½äº§çå½±åãè°è¯çæ¬ç Runtime Library å å«äºè°è¯ä¿¡æ¯ï¼å¹¶éç¨äºä¸äºä¿æ¤æºå¶ä»¥å¸®å©åç°é误ï¼å æ¤æ§è½ä¸å¦åå¸çæ¬ãç¼è¯å¨æä¾ç Runtime Library é常å¾ç¨³å®ï¼ä¸ä¼é æ Release çé误ï¼åæ¯ç±äº Debug ç Runtime Library å 强äºå¯¹é误çæ£æµï¼å¦å å ååé ï¼ææ¶ä¼åºç° Debug æéä½ Release æ£å¸¸çç°è±¡ãåºå½æåºçæ¯ï¼å¦æ Debug æéï¼å³ä½¿ Release æ£å¸¸ï¼ç¨åºè¯å®æ¯æ Bug çï¼åªä¸è¿å¯è½æ¯ Release ççæ次è¿è¡æ²¡æ表ç°åºæ¥èå·²ã
2ãä¼åï¼è¿æ¯é æé误ç主è¦åå ï¼å ä¸ºå ³éä¼åæ¶æºç¨åºåºæ¬ä¸æ¯ç´æ¥ç¿»è¯çï¼èæå¼ä¼ååç¼è¯å¨ä¼ä½åºä¸ç³»åå设ãè¿ç±»é误主è¦æ以ä¸å ç§ï¼
1. 帧æé(Frame Pointer)çç¥ï¼ç®ç§°FPOï¼ï¼å¨å½æ°è°ç¨è¿ç¨ä¸ï¼ææè°ç¨ä¿¡æ¯ï¼è¿åå°åãåæ°ï¼ä»¥åèªå¨åéé½æ¯æ¾å¨æ ä¸çãè¥å½æ°ç声æä¸å®ç°ä¸åï¼åæ°ãè¿åå¼ãè°ç¨æ¹å¼ï¼ï¼å°±ä¼äº§çé误ï¼ä½ Debug æ¹å¼ä¸ï¼æ ç访é®éè¿ EBP å¯åå¨ä¿åçå°åå®ç°ï¼å¦æ没æåçæ°ç»è¶çä¹ç±»çé误ï¼ææ¯è¶çâä¸å¤âï¼ï¼å½æ°é常è½æ£å¸¸æ§è¡ï¼Release æ¹å¼ä¸ï¼ä¼åä¼çç¥ EBP æ åºåæéï¼è¿æ ·éè¿ä¸ä¸ªå ¨å±æé访é®æ å°±ä¼é æè¿åå°åé误æ¯ç¨åºå´©æºã
C++ ç强类åç¹æ§è½æ£æ¥åºå¤§å¤æ°è¿æ ·çé误ï¼ä½å¦æç¨äºå¼ºå¶ç±»å转æ¢ï¼å°±ä¸è¡äºãä½ å¯ä»¥å¨ Release çæ¬ä¸å¼ºå¶å å ¥/Oy-ç¼è¯é项æ¥å ³æ帧æéçç¥ï¼ä»¥ç¡®å®æ¯å¦æ¤ç±»é误ãæ¤ç±»é误é常æï¼MFC æ¶æ¯ååºå½æ°ä¹¦åé误ãæ£ç¡®çåºä¸ºï¼
afx_msg LRESULT OnMessageOwn
(WPARAM wparam, LPARAM lparam);
ON_MESSAGE å®å å«å¼ºå¶ç±»å转æ¢ãé²æ¢è¿ç§é误çæ¹æ³ä¹ä¸æ¯éå®ä¹ ON_MESSAGE å®ï¼æä¸å代ç å å° stdafx.h ä¸ï¼å¨#include "afxwin.h"ä¹åï¼,å½æ°åå½¢é误æ¶ç¼è¯ä¼æ¥éã
#undef ON_MESSAGE
#define ON_MESSAGE(message, memberFxn) /
{
message, 0, 0, 0, AfxSig_lwl, /
(AFX_PMSG)(AFX_PMSGW)
(static_cast< LRESULT (AFX_MSG_CALL /
CWnd::*)(WPARAM, LPARAM) > (&memberFxn)
},
2. volatile ååéï¼volatile åè¯ç¼è¯å¨è¯¥åéå¯è½è¢«ç¨åºä¹å¤çæªç¥æ¹å¼ä¿®æ¹ï¼å¦ç³»ç»ãå ¶ä»è¿ç¨å线ç¨ï¼ãä¼åç¨åºä¸ºäºä½¿ç¨åºæ§è½æé«ï¼å¸¸æä¸äºåéæ¾å¨å¯åå¨ä¸ï¼ç±»ä¼¼äº register å ³é®åï¼ï¼èå ¶ä»è¿ç¨åªè½å¯¹è¯¥åéæå¨çå åè¿è¡ä¿®æ¹ï¼èå¯åå¨ä¸çå¼æ²¡åã
å¦æä½ çç¨åºæ¯å¤çº¿ç¨çï¼æè ä½ åç°æ个åéçå¼ä¸é¢æçä¸ç¬¦èä½ ç¡®ä¿¡å·²æ£ç¡®ç设置äºï¼åå¾å¯è½éå°è¿æ ·çé®é¢ãè¿ç§é误ææ¶ä¼è¡¨ç°ä¸ºç¨åºå¨æå¿«ä¼ååºéèæå°ä¼åæ£å¸¸ãæä½ è®¤ä¸ºå¯ççåéå ä¸ volatile è¯è¯ã
3. åéä¼åï¼ä¼åç¨åºä¼æ ¹æ®åéç使ç¨æ åµä¼ååéãä¾å¦ï¼å½æ°ä¸æä¸ä¸ªæªè¢«ä½¿ç¨çåéï¼å¨ Debug çä¸å®æå¯è½æ©çä¸ä¸ªæ°ç»è¶çï¼èå¨ Release çä¸ï¼è¿ä¸ªåéå¾å¯è½è¢«ä¼åè°ï¼æ¤æ¶æ°ç»è¶çä¼ç ´åæ ä¸æç¨çæ°æ®ãå½ç¶ï¼å®é çæ åµä¼æ¯è¿å¤æå¾å¤ãä¸æ¤æå ³çé误æéæ³è®¿é®ï¼å æ¬æ°ç»è¶çãæéé误çãä¾å¦ï¼
void fn(void)
{
int i;
i = 1;
int a[4];
{
int j;
j = 1;
}
a[-1] = 1;
//å½ç¶é误ä¸ä¼è¿ä¹ææ¾ï¼ä¾å¦ä¸æ æ¯åé
a[4] = 1;
}
j è½ç¶å¨æ°ç»è¶çæ¶å·²åºäºä½ç¨åï¼ä½å ¶ç©ºé´å¹¶æªæ¶åï¼å è i å j å°±ä¼æ©çè¶çãè Release çç±äº iãj 并æªå ¶å¾å¤§ä½ç¨å¯è½ä¼è¢«ä¼åæï¼ä»è使æ è¢«ç ´åã
3. DEBUG ä¸ NDEBUG ï¼å½å®ä¹äº _DEBUG æ¶ï¼assert() å½æ°ä¼è¢«ç¼è¯ï¼è NDEBUG æ¶ä¸è¢«ç¼è¯ãæ¤å¤ï¼TRACE() å®çç¼è¯ä¹å _DEBUG æ§å¶ã
ææè¿äºæè¨é½åªå¨ Debugçä¸æ被ç¼è¯ï¼èå¨ Release çä¸è¢«å¿½ç¥ãå¯ä¸çä¾å¤æ¯ VERIFY()ãäºå®ä¸ï¼è¿äºå®é½æ¯è°ç¨äºassert()å½æ°ï¼åªä¸è¿éå äºä¸äºä¸åºæå ³çè°è¯ä»£ç ãå¦æä½ å¨è¿äºå®ä¸å å ¥äºä»»ä½ç¨åºä»£ç ï¼èä¸åªæ¯å¸å°è¡¨è¾¾å¼ï¼ä¾å¦èµå¼ãè½æ¹ååéå¼çå½æ°è°ç¨çï¼ï¼é£ä¹Releaseçé½ä¸ä¼æ§è¡è¿äºæä½ï¼ä»èé æé误ãåå¦è å¾å®¹æç¯è¿ç±»é误ï¼æ¥æ¾çæ¹æ³ä¹å¾ç®åï¼å 为è¿äºå®é½å·²å¨ä¸é¢ååºï¼åªè¦å©ç¨ VC++ ç Find in Files åè½å¨å·¥ç¨æææ件ä¸æ¾å°ç¨è¿äºå®çå°æ¹åä¸ä¸æ£æ¥å³å¯ãå¦å¤ï¼æäºé«æå¯è½è¿ä¼å å ¥ #ifdef _DEBUG ä¹ç±»çæ¡ä»¶ç¼è¯ï¼ä¹è¦æ³¨æä¸ä¸ã
顺便å¼å¾ä¸æçæ¯VERIFY()å®ï¼è¿ä¸ªå®å è®¸ä½ å°ç¨åºä»£ç æ¾å¨å¸å°è¡¨è¾¾å¼éãè¿ä¸ªå®é常ç¨æ¥æ£æ¥ Windows APIçè¿åå¼ãæäºäººå¯è½ä¸ºè¿ä¸ªåå è滥ç¨VERIFY()ï¼äºå®ä¸è¿æ¯å±é©çï¼å 为VERIFY()è¿åäºæè¨çææ³ï¼ä¸è½ä½¿ç¨åºä»£ç åè°è¯ä»£ç å®å ¨å离ï¼æç»å¯è½ä¼å¸¦æ¥å¾å¤éº»ç¦ãå æ¤ï¼ä¸å®¶ä»¬å»ºè®®å°½éå°ç¨è¿ä¸ªå®ã
4. /GZ é项ï¼è¿ä¸ªé项ä¼å以ä¸è¿äºäºï¼
1. åå§åå åååéãå æ¬ç¨ 0xCC åå§åææèªå¨åéï¼0xCD ( Cleared Data ) åå§åå ä¸åé çå åï¼å³å¨æåé çå åï¼ä¾å¦ new ï¼ï¼0xDD ( Dead Data ) å¡«å 已被éæ¾çå å åï¼ä¾å¦ delete ï¼ï¼0xFD( deFencde Data ) åå§ååä¿æ¤çå åï¼debug çå¨å¨æåé å åçååå å ¥ä¿æ¤å å以é²æ¢è¶ç访é®ï¼ï¼å ¶ä¸æ¬å·ä¸çè¯æ¯å¾®è½¯å»ºè®®çå©è®°è¯ãè¿æ ·åç好å¤æ¯è¿äºå¼é½å¾å¤§ï¼ä½ä¸ºæéæ¯ä¸å¯è½çï¼èä¸ ä½ç³»ç»ä¸æéå¾å°æ¯å¥æ°å¼ï¼å¨æäºç³»ç»ä¸å¥æ°çæéä¼äº§çè¿è¡æ¶é误ï¼ï¼ä½ä¸ºæ°å¼ä¹å¾å°éå°ï¼èä¸è¿äºå¼ä¹å¾å®¹æ辨认ï¼å æ¤è¿å¾æå©äºå¨ Debug çä¸åç° Release çæä¼éå°çé误ãè¦ç¹å«æ³¨æçæ¯ï¼å¾å¤äººè®¤ä¸ºç¼è¯å¨ä¼ç¨0æ¥åå§ååéï¼è¿æ¯é误çï¼èä¸è¿æ ·å¾ä¸å©äºæ¥æ¾é误ï¼ã
2. éè¿å½æ°æéè°ç¨å½æ°æ¶ï¼ä¼éè¿æ£æ¥æ æééªè¯å½æ°è°ç¨çå¹é æ§ãï¼é²æ¢åå½¢ä¸å¹é ï¼
3. å½æ°è¿ååæ£æ¥æ æéï¼ç¡®è®¤æªè¢«ä¿®æ¹ãï¼é²æ¢è¶ç访é®ååå½¢ä¸å¹é ï¼ä¸ç¬¬äºé¡¹åå¨ä¸èµ·å¯å¤§è´æ¨¡æ帧æéçç¥ FPO ï¼é常 /GZ é项ä¼é æ Debug çåºéè Release çæ£å¸¸çç°è±¡ï¼å 为 Release çä¸æªåå§åçåéæ¯éæºçï¼è¿æå¯è½ä½¿æéæåä¸ä¸ªææå°åèæ©çäºéæ³è®¿é®ãé¤æ¤ä¹å¤ï¼/Gm/GFçé项é æé误çæ åµæ¯è¾å°ï¼èä¸ä»ä»¬çæææ¾èæè§ï¼æ¯è¾å®¹æåç°ã
ææ ·âè°è¯â Release ççç¨åº
éå°Debugæåä½Release失败ï¼æ¾ç¶æ¯ä¸ä»¶å¾æ²®ä¸§çäºï¼èä¸å¾å¾æ ä»ä¸æãå¦æä½ çäºä»¥ä¸çåæï¼ç»åé误çå ·ä½è¡¨ç°ï¼å¾å¿«æ¾åºäºé误ï¼åºç¶å¾å¥½ãä½å¦æä¸æ¶æ¾ä¸åºï¼ä»¥ä¸ç»åºäºä¸äºå¨è¿ç§æ åµä¸ççç¥ã
1. åé¢å·²ç»æè¿ï¼DebugåReleaseåªæ¯ä¸ç»ç¼è¯é项çå·®å«ï¼å®é ä¸å¹¶æ²¡æä»ä¹å®ä¹è½åºåäºè ãæ们å¯ä»¥ä¿®æ¹Releaseççç¼è¯é项æ¥ç¼©å°é误èå´ãå¦ä¸æè¿°ï¼å¯ä»¥æRelease çé项é个æ¹ä¸ºä¸ä¹ç¸å¯¹çDebugé项ï¼å¦/MDæ¹ä¸º/MDdã/O1æ¹ä¸º/Odï¼æè¿è¡æ¶é´ä¼åæ¹ä¸ºç¨åºå¤§å°ä¼åã注æï¼ä¸æ¬¡åªæ¹ä¸ä¸ªé项ï¼çæ¹åªä¸ªé项æ¶é误æ¶å¤±ï¼å对åºè¯¥é项ç¸å ³çé误ï¼é对æ§å°æ¥æ¾ãè¿äºé项å¨Project/Settings...ä¸é½å¯ä»¥ç´æ¥éè¿å表éåï¼é常ä¸è¦æå¨ä¿®æ¹ãç±äºä»¥ä¸çåæå·²ç¸å½å ¨é¢ï¼è¿ä¸ªæ¹æ³æ¯æææçã
2. å¨ç¼ç¨è¿ç¨ä¸å°±è¦æ¶å¸¸æ³¨ææµè¯ Release çæ¬ï¼ä»¥å æå代ç 太å¤ï¼æ¶é´åå¾ç´§ã
3. å¨ Debug çä¸ä½¿ç¨ /W4 è¦å级å«ï¼è¿æ ·å¯ä»¥ä»ç¼è¯å¨è·å¾æ大é度çé误信æ¯ï¼æ¯å¦ if( i =0 )å°±ä¼å¼èµ· /W4 è¦åãä¸è¦å¿½ç¥è¿äºè¦åï¼é常è¿æ¯ä½ ç¨åºä¸ç Bug å¼èµ·çãä½ææ¶ /W4 ä¼å¸¦æ¥å¾å¤åä½ä¿¡æ¯ï¼å¦ æªä½¿ç¨çå½æ°åæ° è¦åï¼èå¾å¤æ¶æ¯å¤çå½æ°é½ä¼å¿½ç¥æäºåæ°ãæ们å¯ä»¥ç¨:
#progma warning(disable: )
//ç¦æ¢
//...
#progma warning(default: )
//éæ°å 许æ¥ææ¶ç¦æ¢æ个è¦åï¼æ使ç¨
#progma warning(push, 3)
//设置è¦å级å«ä¸º /W3
//...
#progma warning(pop)
//é设为 /W4
æ¥ææ¶æ¹åè¦å级å«ï¼ææ¶ä½ å¯ä»¥åªå¨è®¤ä¸ºå¯ççé£ä¸é¨å代ç ä½¿ç¨ /W4ã
4. ä½ ä¹å¯ä»¥åDebugä¸æ ·è°è¯ä½ çReleaseçï¼åªè¦å å ¥è°è¯ç¬¦å·ãå¨Project/Settings... ä¸ï¼éä¸ Settings for "Win Release"ï¼éä¸ C/C++ æ ç¾ï¼Category é Generalï¼Debug Info é Program Databaseãåå¨ Link æ ç¾ Project options æåå ä¸ "/OPT:REF" (å¼å·ä¸è¦è¾)ãè¿æ ·è°è¯å¨å°±è½ä½¿ç¨ pdb æ件ä¸çè°è¯ç¬¦å·ã
ä½è°è¯æ¶ä½ ä¼åç°æç¹å¾é¾è®¾ç½®ï¼åéä¹å¾é¾æ¾å°?è¿äºé½è¢«ä¼åè¿äºãä¸è¿ä»¤äººåºå¹¸çæ¯ï¼Call Stackçªå£ä»ç¶å·¥ä½æ£å¸¸ï¼å³ä½¿å¸§æé被ä¼åï¼æ ä¿¡æ¯ï¼ç¹å«æ¯è¿åå°åï¼ä»ç¶è½æ¾å°ãè¿å¯¹å®ä½é误å¾æ帮å©ã