Synchronized实现同步的基础:java的每一个对象都可以作为锁
3中表现形式
- 普通同步方法:锁是当前实例对象
- 静态同步方法:锁是当前类的Class对象
- 同步方法块:锁是Synchronized括号里配置的对象
从JVM看实现原理
JVM是基于进入和退出Monitor对象实现方法同步和代码块同步,但二者实现细节不同。
代码块同步是基于使用monitorenter和monitorexit指令实现的,而方法同步基于另一种(也可以使用这两个指令实现)
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit与之配对是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且仅当一个monitor被持有后,对象处于锁定状态,线程执行到monitorenter时,将会尝试获取对象所对应的monitor所有权,即尝试获得对象的锁。
synchronize锁的升级
java SE1.6中,锁一共有4种状态,由低到高是:无锁态、偏向锁、轻量级锁、重量级锁,锁只能升级但不能降级,不能降级是为了提高获得锁和释放锁的效率。
-
偏向锁
HotSpot作者经研究发现,多数情况,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了使此线程获得锁的代价更低而引入了偏向锁。
-
获得锁过程
线程进入同步块并获得锁时,在对象头和帧栈中的锁记录里存储所偏向的线程ID。以后线程进入和退出同步块时就不需要CAS操作进行加解锁,只需测试对象头的mark word是否存着指向当前线程的偏向锁(线程ID、是否偏向锁位),是,表示线程已获得锁,否则,测试mark word中偏向锁表示是否为1,不是则使用CAS竞争锁,否则尝试使用CAS将对象头的线程ID设置为本线程。
-
释放锁过程
偏向锁使用一种等到竞争出现才释放锁的机制。释放需要等待全局安全点(此时没有正在执行的字节码),先暂停持有偏向锁的线程,检查持有偏向锁的线程是否活着(run或者runnable),否,则设置对象头为无锁,遍历偏向对象的锁记录栈中的锁记录和对象头的mark word要么重新偏向其他线程,要么恢复到无锁标志对象不适合作为偏向锁,唤醒被暂停的线程;是,拥有偏向锁的线程继续执行,偏向锁升级为轻量级锁。
-
关闭偏向锁
java 6和java 7默认启动,关闭会默认进入轻量级锁
-
-
轻量级锁
-
加锁过程
进入同步块时,若同步对象为无锁态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word 并复制其到锁记录空间。虚拟机使用CAS操作尝试将对象的Mark Word更新为指向线程的指针,即用Lock record里的owner指针代替对象的 mark word(Lock Record包含Displaced Mark Word和owner,owner代指线程本身ID)。
替换成功,当前线程获得锁,否则,表示有竞争,当前线程尝试自旋来获取锁 -
释放锁过程
使用CAS操作将Displaced Mark Word替换回原对象,成功,表示无竞争,否则,锁会膨胀为重量级锁。
-
关于自旋
自旋会消耗CPU,为了避免无用的自旋(如已获得锁的线程被阻塞),因而升级成重量级锁,也就是从忙等待到了睡眠状态,不会消耗CPU,仅当持有锁的线程释放了才会唤醒其他等锁线程
-
-
重量级锁
最原始的锁,休眠唤醒机制,造成相应较慢。
重量级锁、轻量级锁和偏向锁之间转换
![](https://img.haomeiwen.com/i10154499/9a3ea612d398292f.png)
锁的优缺点
类别 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁解锁不需要额外的消耗,和执行非同步的方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争, 则会带来额外的锁撤销的消耗 | 适用于长期只有一个线程访问同步块的场景 |
轻量级锁 | 竞争锁的线程不会阻塞, 提高了程序的响应速度 | 竞争的线程会自旋, 消耗CPU | 追求响应时间, 且同步块执行速度快的场景 |
重量级锁 | 竞争锁的线程不自旋, 不会消耗CPU | 竞争锁的线程会阻塞, 响应时间缓慢 | 追求吞吐量, 或者是同步块执行时间较长的时候 |
参考:
https://blog.csdn.net/zhoufanyang_china/article/details/54601311
《java并发编程的艺术》
网友评论