锁机制篇——synchronized
synchronized是java关键字,相当于一把锁,用于解决线程安全问题
一、synchronized的使用
1、什么是锁
锁是一种资源,谁获取了锁,谁就拥有执行锁所管辖(锁住)范围内的代码的权限。
synchronized { 锁住的范围 }
- 方法上修饰:锁住该方法内的所有代码
public synchronized void show(){
锁管辖范围
}
- 代码块上修饰:锁住代码块中的部分
public void show(){
lalala
synchronized(obj){
锁管辖范围
}
lalalla
}
2、谁可以当锁
java是一种面向对象的编程语言,在java中,万事万物皆对象。所以,锁也不例外,必然是某个对象。由于对象可以分为两类:1)实例对象和2)类对象。因此,对应充当锁的对象也可分为两类:1)实例对象锁和类对象锁。
- 实例对象锁:Object obj=new Object()
obj
可以是锁 - 类对象锁:
类名.class
可以是锁
3、谁是锁
当synchronized关键字标注在代码块上时,后面小括号中会明确标注谁是锁(可以是任意对象、可以是类对象,也可以是实例对象;可以是该类也可以不是该类的类对象或实例对象),这个时候谁是锁毋庸置疑。但是当其标注在方法上时,并没有明确指明谁是锁。这种情况下,具体是谁在充当锁呢,小朋友你是否有很多问号???
- 如果是静态方法,锁是该方法所在类的大
class对象
- 如果是非静态的方法,锁是调用该方法的实例对象本身
this
4、使用注意事项
- 只有线程在争抢同一把锁是时,才会成功阻塞其它线程
- 类对象和该类的实例对象充当锁时,它们是两把锁,没有关系,谁也锁不住谁。充当锁的对象都是平等关系,没有谁包含谁的关系。
- 它是可重入锁:可以重复获取
- 它是非公平锁:并不是按照先来先到的原则获取锁资源
二、底层原理
1、字节码层面看synchronized
synchronized在字节码层面相当加了三个字节码指令:一个monitorenter
和两个monitorexit
。它们分别对应加锁和解锁在操作系统层面的一系列命令,本质上synchronized关键字代表的锁机制是OS层面提供的。
【问题】为什么会有两个monitorexit呢?
答:在执行同步代码块时,很有可能发生异常。jvm为了避免发生异常时导致锁资源无法正常释放,进而产生死锁问题,因此有两个释放锁的字节码指令。
2、对象在内存中的组织结构
在jvm运行时内存中,存储的对象并只有包含类中定义的成员变量,还包括对象本身的一些信息(https://blog.csdn.net/weixin_45880263/article/details/106157587)。
对象 = 对象头(对象信息) + 对象体(对象中的成员变量+补齐位)
对象头
- mark word
- Class word: 指向该实例对象大class对象的引用
3、JVM中的锁以及每种锁的运行原理
1)轻量级锁
- 获取锁的线程中会创建一个锁记录, 每个锁记录包含两个信息
- 1)指向锁对象的引用
- 2)锁记录自己的引用 | 00
- 当重复获取锁时,cas操作会失败,不过由于获取锁的线程是同一个,这种失败无关紧要,它会在栈帧中增加锁记录的个数,但是其对应的锁记录的第二个信息存储为null
2)重量级锁
- 当cas失败是由于不同线程获取锁导致的,轻量级锁会升级成重量级锁
- os创建monitor对象,将锁对象的mark word指向monitor对象,monitor中标识锁拥有者的信息指向锁记录
- 将争夺锁资源的线程排队到monitor对象的entrylist中
后面都是锁优化
3)自旋锁
重量级锁存在大量竞争的情况下,会降低线程执行效率,所以jvm通过优化锁机制——增加自旋锁来减少开销。
- 发现当前是重量级锁情况,新来线程开始自旋,自旋过程中如果可以获取锁成功,就避免了线程转换为阻塞态的开销
- 如果发现之前自旋可以成功,jvm会自动地增加自旋次数;如果发现自旋失败,会自动地减少自旋次数,甚至关闭自旋优化功能。
- 自旋只适合多核cpu,单核情况下甚至会降低性能,因为自旋相当于不处理业务逻辑地空转,会占用cpu时间片
- jdk6之后自旋变得智能(自适应),jdk7之后不可人为设置是否支持自旋
4)偏向锁
锁重入会不停执行cas操作,这个消耗也不小,我们希望对此进行优化,引入了偏向锁。
- 首次获取锁,锁对象头mark word中不存储
锁记录引用|00
,而是存储os分配的线程id|101
,这样重入时就不需要进行cas操作 - 如果新线程争抢,偏向锁会升级为轻量级锁
- 注意事项
- 首次获取偏向锁时,偏向锁生效有延迟,可以通过参数
-XX:BiasedLocklyStatusDelay=0
取消延迟 - 开启关闭偏向锁的参数
-XX:+UseBiasedKocking
/-XX:-UseBiasedKocking
- 首次获取偏向锁时,偏向锁生效有延迟,可以通过参数
5)批量重偏向
默认如果偏向锁更改次数超多20,则jvm认为我偏向错了,批量一次性全部重新偏向。
6)消除偏向锁
默认如果偏向锁更改次数超多40,则jvm认为我又偏向错了,竞争太激烈,决定取消偏向锁的支持。
7)锁消除
jvm发现你加了synchronized的代码其实不会发生争夺,就会通过JIT取消加锁
8)锁粗化
锁粒度太细,导致频繁的加锁和解锁过程,JIT可优化让锁范围扩大