介绍
讲解悲观锁的工作原理,特点和作用以及和ReentrantLock的区别
一、 作用
保证同一时刻最多只有1个线程执行 被 Synchronized 修饰的方法 / 代码。保证线程安全,解决多线程中的同步问题
二、原理
- 依赖JVM来实现方法和代码块的同步
- 底层通过一个监视器对象monitor完成,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出异常
三、 锁的类型
Synchronized 用于修饰代码块、类的实例方法 & 静态方法。分为对象锁和类锁。
- 区别
类型 | 锁对象 | 锁数量 | 锁对象的内存区域 |
---|---|---|---|
对象锁 | 实例对象 | 一个 | 栈 |
类锁 | class | 多个 | 方法区,所有线程共享,只有一份 |
四、线程竞争流程
JVM将锁信息保存在ObjectMonitor中,存储了两个队列entrySet和waitSet,分别用于存储竞争锁失败的和调用wait/sleep方法的阻塞线程。
尝试获取锁的线程会进入entry set,获取锁就会变成锁的owner,调用wait/sleep之后就会进入到wait set中,notify()唤醒wait set中的线程之后,对象会再次获取锁owner,如果失败再次进入到wait set,否则线程会继续执行,直到执行结束,释放锁。
五、 使用
对象锁
kotlin
// 形式1:作用在非静态方法上
@Synchronized
fun start() {
// do something
}
fun stop() {
// 形式2:作用在非静态方法代码块中
synchronized(this) {
// do something
}
}
类锁
kotlin
companion object {
// 形式1:作用在静态方法上
@Synchronized
fun stop() {
// do something
}
}
fun start() {
// 形式2:作用在方法块上
synchronized(Action::class.java) {
// do something
}
}
六、使用 ReentrantLock
5.1 特点
5.2 公平锁 & 非公平锁
scss
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用默认构造函数创建非公平锁。否则指定参数选择公平锁、非公平锁。
5.3 不可中断 & 可中断
- lock()类似synchronized没有获取到锁会等待锁释放
kotlin
fun testLock() {
val lock = ReentrantLock()
try {
lock.lock() // 如果被其它资源锁定,会在此等待锁释放
//do something
} finally {
lock.unlock()
}
}
- tryLock()在一段时间内尝试竞争锁。如果超时没有拿到锁,会直接退出竞争
kotlin
fun testTryLock() {
val lock = ReentrantLock()
try {
// 10s 内没有获取到锁返回 false
if (lock.tryLock(10, TimeUnit.SECONDS)) {
try {
//do something
} finally {
lock.unlock()
}
}
} catch (e: InterruptedException) {
println("current thread is interrupted")
}
}
七、synchronized和ReentrantLock比较
synchronized | ReentrantLock | |
---|---|---|
锁实现机制 | 依赖monitor | 依赖AQS |
锁类型 | 非公平锁 (任何一个等待的锁线程都有机会获得锁) | 非公平锁 & 公平锁 (按照申请锁的时间依次获得该锁) |
释放锁 | 自动释放(monitor) | 手动调用unlock() |
锁条件 | 一个 | 通过newCondition可以多个 |
可重入性 | 可 | 可 |
7.1 如何选择
当使用synchronized可以满足你的需求时,优先使用synchronized,因为使用方便,会自动释放锁。JDK1.6 中加入了很多针对锁的优化措施,在性能上两者基本是持平的,因此性能因素不再是选择ReentrantLock的理由了。如果你希望每个线程都有机会获得锁,并且可以预知你的系统是典型的高并发场景,选择ReentrantLock比较合适。