引入
CAS
lock cmpxchg
在硬件层面实现:在操作过程中不允许被其他CPU打断,避免CAS在写数据的时候被其他线程打断,相比操作系统级别的锁,效率要高很多。
加锁才能让多线程的访问变为序列化。
用户态和内核态
对于操作系统来说,普通程序希望操作硬件,需要进行申请。所有指令可以直接被操作系统内核直接访问,而用户进程想要访问硬件资源,需要通过操作系统才能访问。
内核态:执行在内核空间,能够访问所有指令
用户态:只能访问用户能够访问的指令
linux内核工作在ring0,用户进程工作在ring3
syncronized的升级:不同于早期,有时候只需要在用户态进行操作,不需要经过内核态。
int 0x80
向OS申请一个中断的调用
markword
工具:JOL=Java Object Layout,添加 Maven 依赖:
对象的内存布局:当new出一个对象之后,这个对象在内存是怎么分布的?
我们来看HotSpot的实现。
要求对象8字节对齐(对象大小字节数必须是8的整数倍,如果不是,补4个空字节)
8字节markword
4字节classpointer(默认)
4字节instance data(用于存储成员变量)
…关于类方法,方法放在虚方法表里,那是另外一个实现了。
那么,synchronized
做了什么事?
结论:锁信息被记录在了markword里面
JDK8 markword实现表:
锁升级初步
偏向锁
前提:
StringBuffer是同步的
多数代码段在实际运行时只有一个线程,所以没有必要设计竞争机制。
偏向锁
没有必要设计锁竞争机制时,只是把第一个访问的线程的id
写到markword
中,而不去真正的加锁
自旋锁
偏向锁时,有人来竞争锁了,现在操作系统把偏向锁撤销,进行自旋锁(轻量级锁)竞争。
竞争方式:每个人在自己的线程内部生成一个自己LR(Lock Record锁记录),两个线程通过自己的方式尝试将 LR 写门上,竞争成功的开始运行,竞争失败的一直自旋等待。
重量级锁
当必须加锁时,markword中记录的是objectmonitor(JVM用C++写的一个Object)
class文件的字节码
monitorenter(sync开始时)
monitorexit(sync完成或发生异常)
锁重入
synchronized是可重入锁(在未解锁的情况下,可以多次被加锁)。
用来满足子类和父类都是sync的情况。
重入次数必须记录,因为要解锁几次必须要对应。
偏向锁实现:记录在线程栈里,自旋锁->线程栈->每重入一次,LockRecord+=1
重量锁实现:惊动了操作系统,记录在ObjectMonitor字段上
轻量级锁什么时候升级为重量级锁
什么是偏向锁启动/未启动
为什么有自旋锁,还需要有重量级锁?
自旋是占用CPU时间,消耗CPU资源的。如果锁的时间长或者自旋线程多,CPU资源会被大量消耗。这是不划算的。在这种情况下,升级为重量级锁。重量级锁中有等待队列,拿不到锁的进入等待队列,不需要消耗CPU资源(比如竞争队列、执行队列、等待队列waitset)
偏向锁是否一定比自旋锁效率高?
不一定。在明确知道一个资源会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销。这时候应给直接使用自旋锁。
JVM启动过程,会有很多线程竞争(明确知道),所以默认情况,JVM启动时不打开偏向锁(默认时延4秒)。一段时间之后再打开。可以使用Thread.sleep(5000);
之后再new对象验证