synchronized
2020-06-23 本文已影响0人
奔向学霸的路上
线程安全是并发编程中的重点,造成线程安全问题主要诱因又两点,一是存在共享数据(临界资源),二是存在多条线程共同操作共享数据。为了解决这个问题,我们需要一个方案,也就是当存在多个线程操作共享变量的时候,进行加锁控制让同一时刻只有一个线程操作共享变量,其他线程等待,这种方式叫做互斥锁。
synchronized作用
- 保证同一时刻只有一个线程执行某个方法或者代码块
- 保证一个线程的变化被其他线程可见,相当于volatile
synchronized的三种应用方式
- 修饰实例方法,对当前实例加锁,进入前需要获取到当前实例的锁
- 修饰静态方法,对当前类对象加锁,进入前需要获取到当前类对象的锁
- 修饰代码块,对给定对象加锁,进入前需要获取到给定对象的锁
synchronized底层语义
Java对象头
在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充
- 实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组还包括数组的长度,这部分按4字节对齐。
- 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍,它不是必须存在,为了字节对齐,了解即可。
-
头对象:synchronized使用的锁对象是存储在Java对象头里的,JVM中采用2个字节存储对象头(如果是数组分配3字节,一个用来记录数组长度),其主要结构是由Mark Word和Class Metadata Address组成,结构如下:
image.png
其中Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等以下是32位JVM的Mark Word默认存储结构
image.png
由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:
image.png
synchronized锁
synchronized锁是可重入的
偏向锁
大多数情况下,都是单线程访问,不会存在并发的情况,为了减少锁的获取,引入了偏向锁
- 检查当前头对象mark word记录的是否是当前线程ID,如果是,获取到偏向锁;
- 如果不是,CAS替换对象头中线程ID,成功后,获取到偏向锁;
- 如果CAS替换失败,开始偏向锁撤销(等到竞争出现才会撤销);
- 等到竞争后,检查原持有偏向锁的线程状态,如果已经退出代码块,释放锁;
-
等到竞争后,检查原持有偏向锁的线程状态,如果未退出代码块,升级未轻量级锁。
image.png
轻量级锁
轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
image.png
重量级锁
重量级锁标识为10,通过monitor对象来实现,每个对象都存在一个monitor与之关联,monitor存在于每个对象的对象头中,这也就是为什么每个对象都能做锁,且wait/notify/notifyAll等方法存在于Object中的原因。
- 对于同步语句块,通过monitorenter和monitorexit指令来锁定
- 对于同步方法,是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED标识来隐式实现同步的。