synchronized 关键字
用法
Java的每一个对象都可以作为锁
-
普通同步方法,锁是当前实例对象
-
同步静态方法,锁是当前class对象
-
同步代码块,锁是Synchonized括号里面配置的对象
synchronized用的锁存在java对象头里面
java对象头的存储结构
| 锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit标记位 |
|---|---|---|---|---|
| 无锁状态 | 对象hashCode | 对象分代年龄 | 0 | 01 |
| 偏向锁 | 线程ID|Epoch | 对象分代年龄 | 1 | 01 |
| 轻量锁 | 指向栈中锁记录的指针 | 00 | ||
| 重量锁 | 指向互斥量的指针 | 10 | ||
| GC标记 | 空 | 11 |
偏向锁
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)
偏向锁获取过程:
偏向锁的释放:
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,如果该线程处于不活动状态,则将对象头设置为无锁状态,否则将偏向锁标记为轻量锁(00),然后恢复暂停的线程,继续执行同步代码
偏向锁如果受到多个线程访问,会升级为轻量锁(除非并发拥有偏向锁的线程不活动)
偏向锁的标记位是一个开关,0表示关闭偏向锁,1表示打开偏向锁 ,关闭状态时,线程同步从轻量锁开始
轻量锁
轻量锁是存在两条线程竞争时的一种锁形态,偏向锁采用自旋的而不是切换线程状态的方式来实现同步等待,如果自旋次数超过一定次数限制,或者等待过程中添加了新的竞争线程,偏向锁均会升级为重量级锁
轻量级锁不是一定会膨胀为重量级锁,如果同步代码块执行速度很快,当竞争线程还在自旋时就已经退出让出了锁,那么此时锁不会升级
重量级锁
排他锁,其它线程争取锁时会将被切换到阻塞态,获取锁的线程释放锁的时候,会唤醒阻塞态的线程继续竞争。
锁的优缺点
| 锁 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 偏向锁 | 加锁和解锁不需要额外的消耗,执行同步方法和非同步方法相比仅存在纳秒级 | 如果存在线程竞争,会带来额外的开销 | 适合只有一个线程的同步代码 |
| 轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间,同步代码块执行很快 |
| 重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 |