4.2.2 锁的升级过程
- 一开始new 出锁对象时,还没有线程进入临界区,此时是无锁态。
- 有线程进入,则改成偏向锁,同时markword存入线程的id。
- 如果是同一线程则还是偏向锁。
- 当另外线程也进入临界区,请求锁对象,发现对象头已经有偏向锁了。产生了竞争!!那不好意思,先撤销偏向锁,然后两个线程通过CAS自旋的方式开始争抢锁对象,都往锁对象头里面写入自己线程栈的lock record。一旦有线程争抢成功,那么其他线程就会失败,此时锁对象变成了轻量级锁。
- 失败的线程会一直CAS循环下去,此间也可能还会有其他线程参与进来自旋。
- 当自旋次数超过一定值如10次,或者参与自旋的线程数太多。系统会进行干预。
- 这样干耗着会浪费CPU资源,所以干脆升级为重量级锁。其他线程全部进入mutex中的队列中去排队,线程进入wait或者block状态,不消耗CPU。
- 但synchronize修饰的是非公平的队列。
讲了这么多,synchronize的底层到底是怎么实现的?
其实还是 lock cmpxchg
指令。
4.3 volatile
volatile修饰变量后有两个作用: 1,内存可见性 线程间工作内存和主存实现了及时同步 2,防止指令重排 这对这个变量的操作被JMM加入内存屏障来保证指令不会乱序执行。
volatile到底是怎么解决指令重排的??
JVM层通过加入内存屏障,是一个逻辑实现,是jvm的要求规范而已,具体要看汇编语言。
- loadload 屏障 读
- storestore 屏障 写
- loadstore 屏障
- storeload 屏障
四个逻辑。 具体 就是在volatile读/写的前后加入内存屏障,保证顺序执行。内存屏障前后的指令不能重排序!
汇编层面: 最终就是调用了 lock: andl
指令。表示在寄存器中加0操作。
为什么这条指令能实现内存可见和禁止指令重排序??
内存可见性: 该指令能够将当前处理器对应缓存内容刷新到内存,并且是其他处理器的缓存失效。 重排序: 该指令本身就是内存屏障,它前面的指令和后面的指令都不能重排序。
4.4 CAS和原子操作
4.4.1 乐观锁和悲观锁
- 悲观锁: 在访问共享资源的时候总是认为别人会来抢,所以只要访问临界区就直接上锁。通俗讲就是因为怕被抢,所以无脑上锁。比如用synchronize来对临界区上锁。
- 乐观锁: 在访问共享资源时候,认为别的线程不会来抢资源。所以是“无锁”状态。但是可以通过CAS来保证数据的安全。ReentrantLock互斥锁(互斥别人,不互斥自己)
4.4.2 CAS
CAS(compare and swap): 比较并且交换。 目的: 在没有锁的状态下,可以保证多个线程对一个值的更新。
CAS实现思想:
- E:拿到变量当前原始值(期望值)
- V:计算的结果值
- N:再次获取变量的值(当前值)
举个例子: 假设i=0,对i做++操作。 CAS的过程是这样的:
- 拿当i的前值 E=0;
- 计算结果值V=1;
- 再次拿i的当前,有可能如下情况: N=0;N=3(因为某个线程更改了)
- 如果E=N,表示没有被修改,我们可以更新,直接修改i=1。
- 如果E!=N,表示被修改过,我们这次修改就不能执行。然后我们在重头开始,再次比较,最终实现交换。
流程图:
- 实现的本质:
AtomicInteger内部就是通过CAS的方式来保证线程安全的。
内部会调用UnSafe类的方法。
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
//U表示 UnSafe类
return U.getAndAddInt(this, VALUE, -1);
}
UnSafe类直接调用的是C++层的native方法compareAndSwapInt()
:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
// while中 native 方法
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
// native 方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
通过源码发现compareAndSwapInt()
最终会调用c++层的 Atomic::cmpxchg方法,有如下指令:
//_asm_ 表示汇编指令
//LOCK_IF_MP: 如果是multi-processs 多处理器, 现在处理器都是多CPU的。
_asm_ volatile (LOCK_IF_MP(%4) "cmpxchg1 %1, (%3)")...//后面省略
如果是多个处理器则 进行lock。为什么? 多个CPU就会出现多线程同时执行,出现并发问题。
而CAS方法会直接通过汇编指令:lock cmpxchg 指令
来完成CAS真正的操作。 cmpxchg 这个指令也就体现了比较和交换的本质了。
所以, 现在的问题变成 lock cmpxhg 指令
是如何实现线程安全的? 写入的过程是没有原子性保证的。 由 lock 指令来保证原子性。 最终由硬件来支持,硬件怎么实现的啊? 好啦,到这里就可以啦。 硬件通过锁定北桥信号?(我也不清楚了啊)。
4.5 AQS(AbstractQueuedSynchronizer) 抽象队列同步器
高并发编程的核心: AQS。
里面通过维护一个volatile int state变量和一个存储线程的队列(双向链表)来实现同步的。 它本身是一个抽象的类,定义了同步模板方法。具体逻辑需要子类去继承实现。 可通过构造方法传入是否是公平锁。
线程通过CAS去获取state值,state初始值为0,那么拿到锁state=1。 后续如果再有线程进来,那么就封装成Node节点看,然后放到队列中去阻塞,知道之前的线程释放锁。 支持公平锁和非公平锁两种方式。
4.5.1 ReentrantLock和synchronize的区别?
synchronize: 最终要通过用户态到内核态的切换,但是有锁的升级优化。悲观锁
ReentrantLock(jdk1.5后新增的锁): 基于AQS同步机制,其实内部还是通过CAS来获取锁,不用到内核态,轻量级。更加灵活。属于 乐观锁。 需要自己手动try catch,在finally中释放锁。
文末福利
这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、架构师筑基必备技能
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
二、Android百大框架源码解析
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
三、Android性能优化实战解析
- 腾讯Bugly:对字符串匹配算法的一点理解
- 爱奇艺:安卓APP崩溃捕获方案——xCrash
- 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
- 百度APP技术:Android H5首屏优化实践
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 携程:从智行 Android 项目看组件化架构实践
- 网易新闻构建优化:如何让你的构建速度“势如闪电”?
- …
四、高级kotlin强化实战
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
- 从一个膜拜大神的 Demo 开始
- Kotlin 写 Gradle 脚本是一种什么体验?
- Kotlin 编程的三重境界
- Kotlin 高阶函数
- Kotlin 泛型
- Kotlin 扩展
- Kotlin 委托
- 协程“不为人知”的调试技巧
- 图解协程:suspend
五、Android高级UI开源框架进阶解密
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
六、NDK模块开发
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
七、Flutter技术进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
八、微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
全套视频资料:
一、面试合集
总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!