Synchronized原理
1. Synchronized的作用
Synchronized相当于给指定代码段、方法或者类加了一把锁,保证JVM的原子性、可见性和有序性。
问题:Synchronized锁的是什么?
实际上,锁的是一个对象。
2. Synchronized的底层原理
(1)从java源码层面
synchronized(o) //o是一个对象
(2)从字节码层面
monitorenter:monitorexit // 在进入Synchronized代码块之前,JVM必须要先获取到监视器,才能进入代码块,如果获取到了,就将对象引用计数器加1,执行完毕后释放监视器,同时将对象引用计数器减1
(3)从JVM层面
Synchronized锁的是一个对象,那就涉及到对象的内存布局以及锁的机制
3. 对象的内存布局
Mark Word: 固定8B。含有锁信息、GC标记信息,如果调用了hashCode还会有hashCode的记录。
Klass Pointer: 默认4B。指针指向对象所属的类。
实例数据: 存放对象的成员变量。
内存填充【padding】: 如果是64位虚拟机,对象大小必须被8整除,此字段就负责填充字节,保证对象大小都是8的倍数。
查看内存布局的代码:ClassLayout.parseInstance(o).toPrintable();
3.1 Mark Word详解
3.1.1 自旋锁/无锁(CAS)
无锁指得是无内核状态的锁,无需从用户态切换到内核态。
【自旋锁的操作流程】
CAS的底层实现是通过lock cmpxchg指令实现的。
其中cmpxcng并不是原子性的指令,也就是说,如果在指令执行期间,有中断产生,指令执行了一半。因此前面需要加lock保证指令执行期间不允许任何其他CPU打断。
但要注意的是,lock指令在多核CPU的时候才需要。
3.1.2 偏向锁
某一线程把自己的id号贴到mark word上。实际上偏向锁未上锁,只是抢占了资源。
Q1:为什么会有偏向锁?
多数的Synchronized方法,在很多情况下,都只有一个线程在执行,比如说StringBuffer中的append方法. 使用偏向锁是为了在资源没有被其他线程竞争的情况下尽量减少锁带来的性能开销。
Q2: 偏向锁打开一定效率高吗?
不一定. 如果已经明确某一变量存在多线程竞争, 偏向锁肯定会涉及到撤销, 这个时候可以直接用自旋锁即可.
4. Synchronized中锁的升级初步
Q1: 什么情况下偏向锁升级为轻量级锁?
只需要加入另一个线程, 形成竞争关系, 就会升级.
Q2: 什么情况下轻量级锁升级为重量级锁?
当自旋锁自旋10次的时候, 就会从轻量级升级成重量级.
Q3: 有了轻量级锁, 为什么还需要重量级锁?
轻量级锁适用于线程数较少或锁占用时间较短的场景下, 如果某一时刻有大量线程竞争资源, 那就意味着有很多线程不断自旋, 这就会消耗大量CPU资源.
重量级锁适用于线程数较多的场景下, 它通过等待队列, 将等待的线程放入队列中, 不消耗CPU资源, 一旦释放锁, 就从队列中取出线程分配CPU资源.