作用
- 确保线程访问互斥的同步代码
- 确保共享变量的修改能及时可见
- 有效解决重排序问题
Java中的每一个对象都可以作为锁,这是synchronized的基础。
- 普通同步方法,锁是当前实例对象
- 静态同步方法,所示当前类的class对象
- 同步方法块,锁是括号中的对象。
synchronize底层原理
同步代码块和同步方法实现的原理不同
同步代码块:通过添加monitorEnter和monitorExit指令来实现。(显示锁定)
同步方法:通过调用指令读取常量池方法表结构的ACC_SYNCHRONIZED标志实现。(隐式锁定)
任何一个对象都有一个monitor与之关联,当这个monitor被持有之后,处于锁定状态。
- 对象在内存中的布局:
对象头:存放对象的运行时数据
实例数据:存放类的属性信息
对齐填充:字节对齐
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
每一个线程都有一个可用的monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联。对象头markdown字段中的markword指向monitor的的起始地址。
monitor中的信息。
owner:表示持有该锁线程的唯一标识,没有线程持有时为空
entryQ:关联一个系统互斥锁,阻塞所有试图锁住monitor record失败的线程。
RcThis:表示blocked或waiting在该monitor record上的所有线程的个数。
Nest:用来实现重入锁的计数。
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
Candidate:用来避免不必要的阻塞或等待线程唤醒。
- Markdown字段的结构
锁的状态一共有四种:无锁、偏向锁、轻量级锁和重量级锁。锁的级别从低到高。
- Java虚拟机对synchronize的优化
- 偏向锁
如果一个线程获取了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需做任何同步操作。
- 轻量级锁
使用CAS操作来避免重量级锁使用互斥量的开销,CAS失败考虑转换为重量级锁
- 锁消除
检测到不可能存在竞争的共享数据的锁进行锁消除
- 自旋锁
让一个线程在请求一个共享数据的锁时,自旋(忙循环)一段时间,这样就避免了线程的状态切换,减小开销。
- 锁粗化
一系列操作反复对同一个对象加锁,将加锁的范围扩展到着一些零碎操作的外部
- synchronize和ReentrantLock的区别
- 实现:synchronize是Java中的关键字,是在JVM层面上实现的。ReentrantLock是JUC包中实现的锁。
- 加锁,解锁:synchronize在程序完成后会自动释放锁;ReentrantLock需要自己释放锁
- 异常:发生异常synchronize会自动释放锁,而ReentrantLock不会,需要手动在finally块中释放锁。
- 中断:synchronize不能响应中断,ReentrantLock可以响应中断,若长时间没获取锁可以放弃等待。
- 公平:synchronize是非公平锁,ReentrantLock默认是非公平锁,但是也有公平锁的实现,可以通过构造函数来构造公平锁
- 锁是否获取成功:synchronize不能知道锁是否获取成功。ReentrantLock可以通过lock.trylock()返回值知道锁是否获取成功。