随着新的一年钟声逐渐靠近,又到了春天招聘的季节。程序猿们内心也蠢蠢欲动,拨动自己的小算盘,开始筹划着新一轮的换工作热潮。
这一天,程序员们终于想起「面试」支配的恐惧。
众所周知,现在的程序员招聘难度是越来越高了,正所谓面试造火箭,入场扭螺丝~其实吧,我觉得面试造火箭,程序猿技术内卷并不是太糟糕的事情,因为这可以促进行业技术发展,随着时代的迭代更新,程序猿们并不单单只需要熟练运用一门语言,他们更多的是需要结合各种高性能高可用的框架,写出高可扩展性的程序。
技术内卷不可怕,可怕的是比你优秀的人都在努力,你有什么资格不去努力~
今天我准备出这个系列的知识总结,作为我今后对知识的积累,也希望自己能在持续长久的更新下去。其实,这个系列已经烂大街了~随便一搜3y、敖丙,哪个大佬写的不比我强一万倍?但我还是需要自己总结一份属于自己的知识管理,各位有兴趣的可以看看~
基于最近读了《深入理解Java虚拟机》、《Java并发编程实战》、马士兵、3y等书籍文稿视频,我也整理出一份synchronized和volatile的相关知识。
Synchronized关键字
synchronized:
synchronized是重量级锁,每次使用synchronized都是JVM层面向内核OS操作系统进行申请锁的过程。
synchronized关键字通过Javac编译后,会在同步块的前后分别形成monitorenter、monitorexit这两个字节码指令,并且这两个指令都需要关联一个reference参数来指明要锁定和解锁的对象。
Markword对象头:
相较于重量级锁,Java在1.6以后引入的偏向锁、轻量级锁在JVM层级做了锁逻辑,将不依赖以底层OS操作系统,不存在内核上下文切换消耗。
-
Java到底是如何区分识别和使用这些锁的呢?
当标志位分别为 :001无锁,101偏向锁,00轻量级/自旋锁,10重量级锁。其他的参数诸如hashcode,线程指针和LockRecord,将会在锁升级部分用到。
-
对象内部布局是?
内部布局包含4个部分,分别是:markword对象头(8字节)class pointer类型指针(4字节),instance data实例对象,padding对齐(使对象能被8字节整除)
(对象头8字节64位)
CAS:
CAS 英文名为compare and swap,意思是比较并交换,即在并发的场景下不用锁的形式进行修改,Java的实现是通过JVM中的C++的汇编指令实现:lock compxchg。
-
CAS实现
三个变量,当前值E,修改值V,最新值N。只有在E==N的时候才能把V赋值为E。
-
ABA问题
通常做法是加一个版本号,区别当前版本和之前版本不一致。
锁升级:
-
在介绍锁升级之前,这里简单介绍一下各种锁的作用
偏向锁:Java在无竞争的情况下,会优先使用偏向锁,并在Markword中存储当前线程id
轻量级锁:Java在轻度竞争的情况下,会使用轻量级锁,线程通过CAS修改LR指针,轻量 级锁会不断自旋地CAS并获取锁。
重量级锁:Java在重度竞争的情况下,会使用重量级锁,直接调用OS操作系统实现
-
锁升级
1. 当Javac编译一行带synchronized关键字的对象时,在无竞争的情况下首先会优先使用偏向锁,因为经过Java内核开发的调查,大部分并发都不会发生竞争,如果每次都升级成重度锁,那实在是太消耗CPU了
2. 当对象存在其他线程使用,发生了CAS修改,并修改线程指针为LR指针,偏向锁升级为自旋锁(轻量级锁),当轻量级锁遇到了竞争会不断地CAS自旋的修改LR指针,直到获取到锁为止。
3. 当CAS自旋超过了10次,锁升级为重度锁,由OS操作系统通过Object Monitor管理,并维护着waitSet等待队列和entryList竞争队列。
Volatile关键字
与锁相比,volatile变量是一种更轻量级的同步机制,,因为在使用这些变量时不会发生上下文切换或者线程调度操作。
Volatile关键字有两个关键做用:
线程间可见和禁止指令重排。
在物理计算机的并发问题中,处理器和主内存的运算速度是有很大区别的,如果每次要用数据都从主内存中获取,那么效率总是会比较慢的。所以计算机引入了高速缓存,先将数据缓存起来,运算结束后再还给主内存。
所以在这种环境下没有加volatile关键字的变量线程间是不会同步的,而加了volatile以后Java会要求JVM让内存与缓存的数据保持一致,通过内存屏障来实现。
-
在JVM层面:内存屏障通过实现(Load读取,Save保存)LL,SS,LS,SL间的指令屏障不可交换(缓存一致性协议)
-
在CPU层面:Java要求CPU与主内存之间的变量进行一个锁总线的操作,使得其他CPU的高速缓存无法获取到已改变的变量