以前做数据同步是使用syn关键字加锁的,直到juc包的出现,有了cas操作,锁操作效率变高
markword里记录了锁.
锁升级步骤
1,new 普通对象
2.加了syn关键字后,偏向锁
3,一旦竞争激烈,就变成轻量级锁(自旋锁),也有一种可能是轻量级锁未启动直接上升到轻量级锁
4.竞争再加剧,重量级锁(向操作系统申请)
看对象分布的第一组0000 0000的倒数两位
0 0 1 :刚new的
1 0 1:偏向锁
0 0:轻量级
1 0:重量级
偏向锁和轻量级锁是用户空间锁,不需要和操作系统交互,比如stringbuffer,里面所有的方法都加上了syn关键字,但是大多数情况只有一个线程在调用无竞争,如果syn没优化的话,每次调用都要向操作系统申请锁,效率极低.所以哪个线程先来就偏向它.把线程id写到markword上(c++实现里是当前线程指针).
前几位是当前线程指针,后几位是锁标志位
但是当有了锁竞争后,偏向锁就被撤下了切换成自旋锁,偏向锁时,有其他线程来竞争锁,则先把 偏向锁撤销,然后进行 自旋锁(轻量级锁)竞争,所以会有锁撤销性能损耗
开始竞争,每个线程会在自己的线程栈中生成一个叫 LR的Lock Record,用CAS的方式将LR指针设置到锁对象的markword上
前几位是指向线程栈的LR
1.在线程栈中创建一个Lock Record,将其obj(即上图的Object reference)字段指向锁对象。 2.直接通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。如果失败,进入到步骤3。 3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分(Displaced Mark Word)为null,起到了一个重入计数器的作用。然后结束。
JVM虚拟机在每一个竞争线程的栈帧中,建立一个自己的 **锁记录 (Lock Record, LR) **空间,存储锁对象目前 markword 的拷贝.就相当于修改某变量,需要把它复制到线程工作空间一样。竞争线程 使用 CAS 的方式,尝试把被竞争对象的 markword 更新为指向竞争线程 LR 的指针,如果更新成功即代表该线程拥有了锁,锁标志位将转变为 00,表示处于轻量级锁定状态。CAS 是一种乐观锁:cas(v, a, b) 变量v,期待a,修改值bJava 中调用了 native 的 compareAndSwapXXX() 方法每个人在自己的线程内部生成一个自己LR(Lock Record锁记录),两个线程通过自己的方式尝试将 LR 写门上,竞争成功的开始运行,竞争失败的一直自旋等待。实际上是汇编指令 lock cmpxchg,硬件层面实现。向os申请锁,markword中保存objectmonitor
public synchronized void trimToSize() { }这个方法在jvm汇编中会被解析成一个monitorenter的指令与monitorexit.
monitorenter在cpp中的实现,如果使用了偏向锁就快速拿到锁fast enter,如果没有偏向锁就慢的获得slow enter.
fast enter首先如果偏向锁获得成功那就成功,如果偏向锁获得失败,还是会进入slow enter
slow enter是使用atomic::cmpxchg,升级为轻量级锁(自旋锁)
等monitorenter结束就获取锁了
syn必须是可重入的,可以加一次锁,可重入次数必须记录下来,因为要解锁必须要对应加锁次数
LR lock record,重入计数器
偏向锁的重入次数记录在线程栈里,LR再生成一个,解锁就弹出一个LR
自旋的线程超过cpu核心数的一半就要升级重量级了.因为自旋锁会一直自旋,消耗性能.1.6之后由jvm控制
为什么有自旋锁还需要重量级锁
因为自旋锁一直运行,cpu压力大,自旋这件事是占有cpu时间的,如果锁的时间长,自旋线程多,cpu资源被大量消耗,执行空循环,空切换!!
升级成重量级锁的话,重量级的锁objectmonitor.cpp文件中写了objectmonitor的实现,其中有waitset队列.当某个线程要向os申请锁时,锁会把它扔到队列里去,把它阻塞住不让它执行
问题:偏向锁是不是一定比自旋锁效率高
不是,只有一个线程的时候效率高,当多个线程的时候不一定效率高
偏向锁会涉及锁撤销,明知道有很多线程竞争的话应该直接关闭偏向锁
jvm启动过程中会有很多线程竞争,所以默认启动不打开偏向锁,启动完了再打开
轻量级锁不会wait,直接升级重量级锁
syn通过jvm汇编monitorenter和monitorexit保证线程安全
首先object的对象头markword保存着关于锁的信息,
在 monitorenter 函数内部的实现为:如果打开了偏向锁,则进入 fast_enter, 在 safepoint情况下,尝试获取偏向锁,成功则返回,失败则进入 slow_enter, 升级为自旋锁,如果自旋锁失败,则膨胀 inflate 成为重量级锁。重量级锁的代码在 syncronizer.cpp 中,里面调用了 linux 内核的一些实现方法。
每个对象都与一个 monitor 相关联。当且仅当拥有所有者时(被拥有),monitor才会被锁定。执行到monitorenter指令的线程,会尝试去获得对应的 monitor,如下:
每个对象维护着一个记录着被锁次数的计数器, 对象未被锁定时,该计数器为0。线程进入monitor(执行monitorenter 指令)时,会把计数器设置为1。当同一个线程再次获得该对象的锁的时候,计数器再次自增,这就是锁重入。当其他线程想获得该 monitor 的时候,就会阻塞,直到计数器为0才能成功。
X86 : lock cmpxchg / xxxlock 是处理多处理器之间的总线锁问题Object Monitor机制就是synchronized同步块锁机制升级为“重量级锁”后的工作机制,